--------------------------------------------------------------------------
-- $File: //scripts/gamerules/InstantAction.lua $
-- $Revision$
-- $Date$
-- $Author$
--
-- $Id$
--------------------------------------------------------------------------
-- This file is maintained as part of the SSM AEGIS project. All additions
-- are the sole work of their authors to which all copyright and ownership
-- resides. However, each author grants license for the use of his or her
-- work to be a part of the SSM AEGIS project.
--
--  (C), Daniel 'Rod-Serling' Phelps, 2008
--  (C), Terry 'Charlie' Reinert, 2008
--------------------------------------------------------------------------
--  This work is based off of:
--  (C), Ralf 'Hawkeye' Ullrich, 2008
--  InstantAction.lua,v 1.2.2.5 2008/09/24 23:47:08
--
--  Crytek Source File.
--  Copyright (C), Crytek Studios, 2001-2004.
--------------------------------------------------------------------------


-- =============================================================================
-- Load dependent scripts.
-- =============================================================================
Script.LoadScript("scripts/gamerules/singleplayer.lua", 1, 1);

Script.LoadScript("scripts/astrada/PermaBan.lua", 1, 1);

-- =============================================================================
-- Define the InstantAction object. This object is derived from the
-- SinglePlayer class. Once defined, various parameters can be
-- configured such as timers, player settings, and other global parameters for
-- the game mode.
--
-- **IMPORTANT**
-- The TeamInstantAction class is derived from this class. It calls many of the
-- functions defined in this class. Additionally, the PowerStruggle class is
-- derived from the TeamInstantAction class and also calls function in both that
-- class and this one. Thus, any changes you make to this class could very well
-- have a major impact on ALL game modes! Tread carefully!!
-- =============================================================================
InstantAction = new(SinglePlayer);
InstantAction.States = { "Reset", "PreGame", "InGame", "PostGame", };

InstantAction.MIN_PLAYER_LIMIT_WARN_TIMER = 15; -- player limit warning timer
InstantAction.WEAPON_ABANDONED_TIME       = 45;

InstantAction.FORCERESPAWN_TIMERID		= 1060;

InstantAction.NEXTLEVEL_TIMERID = 1050;
InstantAction.NEXTLEVEL_TIME    = 17000;
InstantAction.ENDGAME_TIMERID   = 1040;
InstantAction.ENDGAME_TIME      = 5000;
InstantAction.TICK_TIMERID      = 1010;
InstantAction.TICK_TIME         = 1000;

--
-- Define synched storage keys. These keys are much like network
-- data identifiers that specify the type of data being sent
-- in the message. These keys are used to sync player data with
-- the server and all the other clients attached to the game.
--
-- DO NOT CHANGE THESE VALUES!!!
--
InstantAction.SCORE_KILLS_KEY     = 100;
InstantAction.SCORE_DEATHS_KEY    = 101;
InstantAction.SCORE_HEADSHOTS_KEY = 102;
InstantAction.SCORE_PING_KEY      = 103;
InstantAction.SCORE_LAST_KEY      = 104;  -- make sure this is always the last one

InstantAction.DamagePlayerToPlayer =
{
    helmet      = 1.25,
    kevlar      = 1.15,

    head        = 1.68,
    torso       = 1.15,
    arm_left    = 0.96,
    arm_right   = 0.96,
    leg_left    = 0.96,
    leg_right   = 0.96,

    foot_left   = 0.96,
    foot_right  = 0.96,
    hand_left   = 0.96,
    hand_right  = 0.96,
    assist_min  = 0.8,
};


-- =============================================================================
-- Net Expose
--
-- TODO: I believe these functions having something to do with the data
--       that is synched across the network but I am not sure exactly how
--       this happens. I will put what I do know here and will update it
--       in the future whenever I get it fully figured out.
--
--       The methods listed in the ClientMethods array are all defined in
--       other scripts using the .Client scope.
--
--       The first two parameters are common to all the methods listed below.
--       Param1 is either RELIABLE_ORDERED or RELIABLE_UNORDERED and Param2
--       is either NO_ATTACH or POST_ATTACH. The remaining parameters match
--       the types in the actual function definition in the other scripts.
-- =============================================================================
Net.Expose {
    Class = InstantAction,

    ClientMethods =
    {
        ClSetupPlayer          = { RELIABLE_UNORDERED, NO_ATTACH, DEPENTITYID, },
        ClSetSpawnGroup        = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, },
        ClSetPlayerSpawnGroup  = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, ENTITYID },
        ClSpawnGroupInvalid    = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, },
        ClVictory              = { RELIABLE_ORDERED, POST_ATTACH, ENTITYID, },
        ClNoWinner             = { RELIABLE_ORDERED, POST_ATTACH, },

        ClStartWorking         = { RELIABLE_ORDERED, POST_ATTACH, ENTITYID; STRINGTABLE },
        ClStepWorking          = { RELIABLE_ORDERED, POST_ATTACH, INT8 },
        ClStopWorking          = { RELIABLE_ORDERED, POST_ATTACH, ENTITYID, BOOL },
        ClWorkComplete         = { RELIABLE_ORDERED, POST_ATTACH, ENTITYID, STRINGTABLE },

        ClClientConnect        = { RELIABLE_UNORDERED, POST_ATTACH, STRING, BOOL },
        ClClientDisconnect     = { RELIABLE_UNORDERED, POST_ATTACH, STRING, },
        ClClientEnteredGame    = { RELIABLE_UNORDERED, POST_ATTACH, STRING, },
        ClTimerAlert           = { RELIABLE_UNORDERED, POST_ATTACH, INT8 },
    },

    ServerMethods =
    {
        RequestRevive          = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, },
        RequestSpawnGroup      = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, ENTITYID },
        RequestSpectatorTarget = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, INT8 },
    },

    ServerProperties =
    {
    },
};

-- =============================================================================
-- Define the audio alert messages sent based on remaining game time.
-- =============================================================================
InstantAction.SoundAlert=
{
    Radio =
    {
            timer2m  = "mp_american/us_commander_mission_2_minute_warning_01",
            timer1m  = "mp_american/us_commander_mission_1_minute_warning_02",
            timer30s = "mp_american/us_commander_mission_30_second_03",
            timer5s  = "mp_american/us_commander_final_countdown_01",
    },
}


-- =============================================================================
-- Function: InstantAction:PlayRadioAlert
--
-- This function is called to set a radio alert to play for a client.
--
-- \param[in] alertName
-- The name of the alert to queue. This name is found in the SoundAlert table
-- defined above.
--
-- \param[unused] teamId
-- The id of the team that this alert should be played for. Note that it is
-- unused in InstantAction as there are no teams in IA!
-- =============================================================================
function InstantAction:PlayRadioAlert(alertName, teamId)
        local alert=self.SoundAlert.Radio[alertName];
        if (alert) then
            self:QueueVoice(alert, bor(SOUND_LOAD_SYNCHRONOUSLY, SOUND_VOICE), SOUND_SEMANTIC_MP_CHAT);
        end
end


-- =============================================================================
-- Function: InstantAction:QueueVoice
--
-- This function adds a sound to the queue for playback on the client.
--
-- \param[in] soundName
-- The name of the sound to play.
--
-- \param[in] soundFlags
-- Flags used to configure aspects of the sound playback.
--
-- \param[in] soundSemantics
-- Semantics relating to the sound playback.
--
-- \param[in] soundGap
-- \param[in] endProc
-- \param[in] endProcParam
-- =============================================================================
function InstantAction:QueueVoice(soundName, soundFlags, soundSemantics, soundGap, endProc, endProcParam)
    if (not self.voiceQueue) then
        self.voiceQueue={};
    end
    local queue=self.voiceQueue;
    table.insert(queue, {
        name=soundName,
        flags=soundFlags,
        semantics=soundSemantics,
        gap=soundGap,
        proc=endProc,
        param=endProcParam,
    });
end


-- =============================================================================
-- Function: InstantAction:UpdateVoiceQueue
--
-- This function updates the voice queue by playing a queued sound or removing
-- an expired sound from the queue.
--
-- \param[in] frameTime
-- The current frame time of the game.
-- =============================================================================
function InstantAction:UpdateVoiceQueue(frameTime)
    if (not self.voiceBusy) then
        self:PlayQueueFront();
    else
        local front=self.voiceQueue[1];
        if (front and (_time>=front.endTime) and (not Sound.IsPlaying(front.soundId))) then
            table.remove(self.voiceQueue, 1);
            self.voiceBusy=false;
            if (front.proc) then
                front.proc(front.param);
            end
            self:PlayQueueFront();
        end
    end
end


-- =============================================================================
-- Function: InstantAction:PlayQueueFront
--
-- This function plays the sound at the front of the queue.
-- =============================================================================
function InstantAction:PlayQueueFront()
    if (not self.voiceQueue) then
        return;
    end
    local front=self.voiceQueue[1];
    if (front) then
        local soundId=Sound.Play(front.name, g_Vectors.v000, front.flags, front.semantics);
        if (soundId) then
            front.endTime=_time+Sound.GetSoundLength(soundId);
            front.soundId=soundId;
            if (front.gap and front.gap>0) then
                front.endTime=front.endTime+front.gap;
            end
            self.voiceBusy=true;
        else
            front.endTime=0;
        end
    end
end


-- =============================================================================
-- Function: InstantAction:ClearVoiceQueue
--
-- This function clears all sounds from the voice queue.
--
-- =============================================================================
function InstantAction:ClearVoiceQueue()
    self.voiceBusy=nil;
    self.voiceQueue=nil;
end


-- =============================================================================
-- Function: InstantAction:IsMultiplayer
--
-- \return
-- This function always returns true as there is no single player IA game mode.
-- =============================================================================
function InstantAction:IsMultiplayer()
    return true;
end


-- =============================================================================
-- Function: InstantAction:CheckPlayerScoreLimit
--
-- This function determines whether the player specified by the passed in
-- playerId is the winner of the match. If another player is found in the
-- player list with a score higher than the passed in player score, the function
-- exits doing nothing. If no higher score is found then the game is ended with
-- the passed in player as the winner.
--
-- \param[in] playerId
-- The id of the player in question.
--
-- \param[in] score
-- The score of the player with the passed in playerId.
-- =============================================================================
function InstantAction:CheckPlayerScoreLimit(playerId, score)
    if (self:GetState() and self:GetState()~="InGame") then
        return;
    end

    local fraglimit=self.game:GetFragLimit();
    local fraglead=self.game:GetFragLead();

    if ((fraglimit > 0) and (score >= fraglimit)) then
        if (fraglead > 1) then
            local players=self.game:GetPlayers(true);
            if (players) then
                for i,player in pairs(players) do
                    if (player.id ~= playerId) then
                        if (self:GetPlayerScore(player.id)+fraglead > score) then
                            return;
                        end
                    end
                end
            end
        end

        self:OnGameEnd(playerId, 3);
    end
end


-- =============================================================================
-- Function: InstantAction:UpdateClAlerts
--
-- This function determines which timer alert sound to play based on the
-- remaining game time.
-- =============================================================================
function InstantAction:UpdateClAlerts()
if (racecountdown) then self.allClients:ClTimerAlert(5); return end
    if (self.game:IsTimeLimited() and self:GetState()=="InGame") then
        local rt=math.floor(self.game:GetRemainingGameTime());
        if ((not self.lastTimerAlert) or (rt~=self.lastTimerAlert)) then
            if (rt==120 or rt==60 or rt==30 or rt==5) then
                self.lastTimerAlert=rt;
                self.allClients:ClTimerAlert(rt);
            end
        end
    end
end


-- =============================================================================
-- Function: InstantAction:CheckTimeLimit
--
-- This function checks to see if the game time has run out indicating an end
-- to the match. The player table is searched to find the player with the highest
-- score. If there is a tie for first place then the player with the least
-- amount of deaths is the winner. If there is more than one player with the
-- highest score and the least number of deaths then the game is a draw.
-- =============================================================================
function InstantAction:CheckTimeLimit()
    if (self.game:IsTimeLimited() and (self:GetState()=="InGame")) then
        if (self.game:GetRemainingGameTime()<=0) then

            --
            -- Find the player with the highest score.
            --
            local maxScore=nil;
            local maxId=nil;
            local draw=false;

            local players=self.game:GetPlayers(true);
            if (players) then
                for i,player in pairs(players) do
                    local score=self:GetPlayerScore(player.id);
                    if (not maxScore) then
                        maxScore=score;
                    end
                    if (score>=maxScore) then
                        if ((maxId~=nil) and (maxScore==score)) then
                            --
                            -- There was already a player with the same score
                            -- so set draw to true to indicate there is a tie.
                            -- This may get set to false on another pass through
                            -- the for loop if a player is found with a higher
                            -- score than the current max score.
                            --
                            draw=true;
                        else
                            --
                            -- A player has been found that has a higher score
                            -- than any other player looked at so far.
                            --
                            draw=false;
                            maxId=player.id;
                            maxScore=score;
                        end
                    end
                end

                --
                -- If there's a draw, check for lowest number of deaths to
                -- determine the winner. If the tied players all have the same
                -- number of deaths then the game is a draw and there is no
                -- winner.
                --
                if (draw) then
                    local minId=nil;
                    local minDeaths=nil;

                    for i,player in pairs(players) do
                        local score=self:GetPlayerScore(player.id);

                        --
                        -- We only want to look at the players that are tied for
                        -- the highest score. So if the player score doesn't
                        -- match the high score found above, skip them.
                        --
                        if (score==maxScore) then
                            local deaths=self:GetPlayerDeaths(player.id);
                            if (not minDeaths) then
                                minDeaths=deaths;
                            end

                            if (deaths<=minDeaths) then
                                if ((minId~=nil) and (minDeaths==deaths)) then
                                    --
                                    -- More than one player has the least number
                                    -- of deaths so indicate there is a tie.
                                    -- This may get set to false later in the
                                    -- for loop if another player is found to
                                    -- have less deaths than the other players
                                    -- that are tied with the highest score.
                                    --
                                    draw=true;
                                else
                                    --
                                    -- A player was found with less deaths than
                                    -- the current minimum death count.
                                    --
                                    draw=false;
                                    minId=player.id;
                                    minDeaths=deaths;
                                end
                            end
                        end
                    end

                    if (not draw) then
                        --
                        -- Set the id of the player with the highest score and
                        -- the lowest death. This can only be done if there is
                        -- no tie (i.e. the game is not a draw).
                        --
                        maxId=minId;
                    end
                end
            end

            --
            -- End the game accordingly based on whether it is a draw or if
            -- there is a winner.
            --
            if (not draw) then
                self:OnGameEnd(maxId, 2);
            else
                self:OnGameEnd(nil, 2);
            end
        end
    end
end


-- =============================================================================
-- Function: InstantAction:PlayerCountOk
--
-- This function checks to see if there are enough players to start the game.
--
-- \return
-- True if the number of players on the server minus the spectators is higher
-- than the minimum number of players required to start the game.
-- =============================================================================
function InstantAction:PlayerCountOk()
    local preGame=self:GetState()=="PreGame";

    if ((self.game:GetPlayerCount(preGame)-self.game:GetSpectatorCount(preGame)) < self.game:GetMinPlayerLimit()) then
        return false;
    end

    return true;
end


-- =============================================================================
-- Function: InstantAction:OnGameEnd
--
-- This function is called when the game ends. It declares the winner or a draw
-- with a message to every client and then changes the state of the game to
-- PostGame.
--
-- \param[in] winningPlayerId
-- The id of the winning player. Could be nil if there is no winner.
--
-- \param[unused]type
-- =============================================================================
function InstantAction:OnGameEnd(winningPlayerId, type)
    
    --
    -- Send a message to all clients stating the game results and then call the
    -- proper function on the client that handles the game over event.
    --
    if (winningPlayerId) then
        local playerName=EntityName(winningPlayerId);
--        self.game:SendTextMessage(TextMessageCenter, "@mp_GameOverWinner", TextMessageToAll, nil, playerName);
        self.allClients:ClVictory(winningPlayerId);
    else
--        self.game:SendTextMessage(TextMessageCenter, "@mp_GameOverNoWinner", TextMessageToAll);
        self.allClients:ClNoWinner();
    end

    --
    -- End the game and go into PostGame state.
    --
    self.game:EndGame();
    self:GotoState("PostGame");
end


-- =============================================================================
-- Function: InstantAction:ResetTime
--
-- This function resets the game time.
-- =============================================================================
function InstantAction:ResetTime()
    self.game:ResetGameTime();
end


-- =============================================================================
-- Function: InstantAction.Server:OnInit
--
-- This function initializes the game mode.
-- =============================================================================
function InstantAction.Server:OnInit()
    SinglePlayer.Server.OnInit(self);

    self.isServer=CryAction.IsServer();
    self.isClient=CryAction.IsServer();

    self.killHit={};
    self.channelSpectatorMode={}; -- keep track of spectators
    
    XGetModFiles("IA");

    self:Reset(true);
end


-- =============================================================================
-- Function: InstantAction.Server:OnStartGame
--
-- This function is called when the game starts. It calls some configuration
-- functions for SSM and then starts the periodic ticker.
-- =============================================================================
function InstantAction.Server:OnStartGame()
    LogInfo("[AEGIS] InstantAction.Server.OnStartGame called");
    XScanServerLog();
    XResetConfig();
    XPrepareMap();
    XSafeCall(XCfgVar.OnNewMap, XVar.currentMapInfo.mapRules, XVar.currentMapInfo.mapName);
    --XGetChatCommandFiles();

    self:StartTicking();
end


-- =============================================================================
-- Function: InstantAction:OnReset
--
-- This function resets the game mode based on whether this is the server or
-- a client.
-- =============================================================================
function InstantAction:OnReset()
    if (self.isServer) then
        if (self.Server.OnReset) then
            self.Server.OnReset(self);
        end
    end

    if (self.isClient) then
        if (self.Client.OnReset) then
            self.Client.OnReset(self);
        end
    end
end


-- =============================================================================
-- Function: InstantAction.Server:OnReset
--
-- This function is the server side onReset function.
-- =============================================================================
function InstantAction.Server:OnReset()
    self:Reset();
end


-- =============================================================================
-- Function: InstantAction.Client:OnReset
--
-- This function is the client side onReset function.
-- =============================================================================
function InstantAction.Client:OnReset()
end


-- =============================================================================
-- Function: InstantAction:Reset
--
-- This function resets the game mode.
--
-- \param[in] forcePregame
-- If true then the game is reset into PreGame state instead of InGame state.
-- =============================================================================
function InstantAction:Reset(forcePregame)
    self:ResetTime();
    AEGIS.PlayerInformationByProfile = {}; --Clear the infromation table on reset.

    if ((self:PlayerCountOk() and (not forcePregame)) or (self.forceInGame)) then
        self:GotoState("InGame");
    else
        self:GotoState("PreGame");
    end
    self.forceInGame=nil;

    self.works={};
end


-- =============================================================================
-- Function: InstantAction:RestartGame
--
-- This function resets the game mode.
--
-- \param[in] forceInGame
-- This gets copied into the InstantAction.forceInGame parameter that ultimately
-- decides whether the reset ends up in PreGame mode or InGame mode.
-- =============================================================================
function InstantAction:RestartGame(forceInGame)
    self:GotoState("Reset");

    self.game:ResetEntities();

    if (forceInGame) then
        self.forceInGame=true;
    end
end


-- =============================================================================
-- Function: InstantAction.Client:OnActorAction
--
-- This function is called to handle an action by a player that is spectating
-- or dead. The actions could be requesting a respawn (revive) or changing
-- the target that is being spectated.
--
-- \param[in] player
-- The entity of the player.
--
-- \param[in] action
-- The action that the player performed.
--
-- \param[in] activation
-- The type of action that the player performed (i.e. "pressed").
--
-- \param[unused] value
--
-- \return
-- True if the action was handled or false if not.
-- =============================================================================
function InstantAction.Client:OnActorAction(player, action, activation, value)
    --
    -- If the attack1 button is pressed and the player is dead and not a
    -- spectator then request revive for the player so they can respawn.
    --
    if ((action == "attack1") and (activation == "press")) then
        if ((player:IsDead() and player.actor:GetSpectatorMode()==0) or player.actor:GetSpectatorMode()==3) then
            self.server:RequestRevive(player.id);
            return false;
        end
    --
    -- Handle actions relating to spectating for dead players or spectators.
    --
    elseif((action == "next_spectator_target") and (activation == "press")) then
        if((player:IsDead() and self.game:GetTeamCount() > 1) or player.actor:GetSpectatorMode() == 3) then
                self.server:RequestSpectatorTarget(player.id, 1);
        end
    elseif((action == "prev_spectator_target") and (activation == "press")) then
        if((player:IsDead() and self.game:GetTeamCount() > 1) or player.actor:GetSpectatorMode() == 3) then
                self.server:RequestSpectatorTarget(player.id, -1);
            end
    elseif((action == "cycle_spectator_mode") and (activation == "press")) then
        -- disallow changing mode if map or scoreboard open
        if(self.game:CanChangeSpectatorMode(player.id)) then
            -- if not on a team, can cycle through modes
            -- if on a team and dead, only 3rd person mode for friendlies (to prevent cheating viewing other team)
            if(self.game:GetTeam(player.id) ~= 0 and player.actor:GetSpectatorMode() == 3) then
                self.server:RequestSpectatorTarget(player.id, 1);
            else
                local mode = player.actor:GetSpectatorMode();
                local target = 0;
                if(mode ~= 0) then
                    mode = mode + 1;
                    if(mode > 3) then
                        mode = 1;
                    end
                    if(mode == 3) then
                        self.server:RequestSpectatorTarget(player.id, 1);
                    else
                        self.game:ChangeSpectatorMode(player.id, mode, NULL_ENTITY);
                    end
                end
            end
        end
    end

    return true;
end


-- =============================================================================
-- Function: InstantAction.Client:OnDisconnect
--
-- This function is called when a client disconnects from a server.
--
-- \param[unused] cause
-- \param[unused] desc
-- =============================================================================
function InstantAction.Client:OnDisconnect(cause, desc)
--  Game.ShowMainMenu();
--  System.ShowConsole(1);
end


-- =============================================================================
-- Function: InstantAction.Server:OnClientConnect
--
-- This function is called on the server when a client connects.
--
-- \param[in] channelId
-- The id of the channel the player is connecting on.
--
-- \param[in] reset
--
--
-- \param[in] name
-- The name of the player connecting.
--
-- \return
-- The entity of the connecting player.
-- =============================================================================
function InstantAction.Server:OnClientConnect(channelId, reset, name)
    local player = self:SpawnPlayer(channelId, name);

    if (not reset) then
        self.game:ChangeSpectatorMode(player.id, 2, NULL_ENTITY);

        if (not CryAction.IsChannelOnHold(channelId)) then
            self:ResetScore(player.id);
            self.otherClients:ClClientConnect(channelId, player:GetName(), false);
        else
            self.otherClients:ClClientConnect(channelId, player:GetName(), true);
        end
    else
        if (not CryAction.IsChannelOnHold(channelId)) then
            self:ResetScore(player.id);
        end

        local specMode=self.channelSpectatorMode[channelId] or 0;
        local teamId=self.game:GetChannelTeam(channelId) or 0;

        if (specMode==0 or teamId~=0) then
            self.game:SetTeam(teamId, player.id); -- make sure he's got a team before reviving

            self.Server.RequestSpawnGroup(self, player.id, self.game:GetTeamDefaultSpawnGroup(teamId) or NULL_ENTITY, true);
            self:RevivePlayer(player.actor:GetChannel(), player);
        else
            self.Server.OnChangeSpectatorMode(self, player.id, specMode, nil, true);
        end
    end
    
    --
    -- If SSM is configured to send a chat message whenever a player joins or
    -- leaves the server then send the message now.
    --
    if (XCfgVar.sendChatOnJoinLeave) then
        XSendChatToAll(nil,"%q joined the game.", player:GetName());
    end

	if (XCfgVar.sendWelcomeConsole) then
		XSendConsoleMessageToPlayer(player, "[AEGIS] Current Messages:");
		for i,msg in pairs(XCfgVar.advertisement) do
			XSendConsoleMessageToPlayer(player, "[AEGIS]$6 %s", msg);
		end
	end
	
	player.info = {
		slot = "-1",
		guid = "",
		ip = "0",
		port = "0",
		status = "",
		power = "0",
		os = "",
		name = "",
		channel = "-1",
		host = "",
		profile = "0",
		buildInfo = 1,
	};
	if(XVar.punkbusterDetected) then
        player.info.pbUpdated = -1;
	end

    return player;
end


-- =============================================================================
-- Function: InstantAction.Server:OnClientDisconnect
--
-- This function is called on the server when a client disconnects.
--
-- \param[in] channelId
-- The id of the channel the player was connected on.
-- =============================================================================
function InstantAction.Server:OnClientDisconnect(channelId)
    local player=self.game:GetPlayerByChannelId(channelId);

    self.channelSpectatorMode[player.actor:GetChannel()]=nil;
    self.works[player.id]=nil;

    self.otherClients:ClClientDisconnect(channelId, player:GetName());
    
    --
    -- If SSM is configured to send a chat message whenever a player joins or
    -- leaves the server then send the message now.
    --
    
    -- save scores for reconnect
	if (XCfgVar.usePersistantScores) then
		local profile = XGetIdentityValue(player, "profile");
		if (profile~="0") then
			if (not AEGIS.PlayerInformationByProfile[profile]) then
	            AEGIS.PlayerInformationByProfile[profile] = {};
			end
			if (not AEGIS.PlayerInformationByProfile[profile].scores) then
			    AEGIS.PlayerInformationByProfile[profile].scores = {};
			end
			AEGIS.PlayerInformationByProfile[profile].scores = {
				[self.SCORE_KILLS_KEY] = self.game:GetSynchedEntityValue(player.id, self.SCORE_KILLS_KEY) or 0,
				[self.SCORE_DEATHS_KEY] = self.game:GetSynchedEntityValue(player.id, self.SCORE_DEATHS_KEY) or 0,
				[self.SCORE_HEADSHOTS_KEY] = self.game:GetSynchedEntityValue(player.id, self.SCORE_HEADSHOTS_KEY) or 0,
			};
		end
	end
    
    if (XCfgVar.sendChatOnJoinLeave) then
        XSendChatToAll(nil,"%q left the game.", player:GetName());
    end
end


-- =============================================================================
-- Function: InstantAction.Server:OnClientEnteredGame
--
-- This function is called on the server when a client enters the game.
--
-- \param[in] channelId
-- The id of the channel that the player is connected on.
--
-- \param[in] player
-- The object of the player that is entering the game.
--
-- \param[in] reset
--
-- =============================================================================
function InstantAction.Server:OnClientEnteredGame(channelId, player, reset)
    local onHold=CryAction.IsChannelOnHold(channelId);
    if ((not onHold) and (not reset)) then
        self.game:ChangeSpectatorMode(player.id, 2, NULL_ENTITY);
    elseif (not reset) then
        if (player.actor:GetHealth()>0) then
            player.actor:SetPhysicalizationProfile("alive");
        else
            player.actor:SetPhysicalizationProfile("ragdoll");
        end
    end
    
    --1.4 Perma-Ban
    
    if (XCfgVar.useBanSystem) then
            XIsBanned(player);
    end

    if (not reset) then
        self.otherClients:ClClientEnteredGame(channelId, player:GetName());
    end

    self:SetupPlayer(player);

    if ((not g_localActorId) or (player.id~=g_localActorId)) then
        self.onClient:ClSetupPlayer(player.actor:GetChannel(), player.id);
    end
    
	--1.4 Usergroup Messages
	
        player.WelcomeMessage = _time;
        player.welcomeMessageSent = false;

	
	--1.3
	if (XCfgVar.SaveMessagesToInbox) then
        local id = player.info.profile;
        if (XSavedMessages.id) then
    	   Script.SetTimer(2000,
                function()
                    for i,msg in pairs(XSavedMessages.id) do
                        XSendConsoleMessageToPlayer(player,XFormat(msg));
                    end
                    XSendBigCenterMessageToPlayer(player,XFormat("You have mail, check your console",player:GetName()));
    	        end);
        end
    end
end


-- =============================================================================
-- Function: InstantAction.Server:OnChangeSpectatorMode
--
-- This function is called when a player changes spectator modes.
--
-- \param[in] playerId
-- The id of the player changing modes.
--
-- \param[in] mode
-- The mode the player is switching to.
--
-- \param[in] targetId
-- The id of the target the player is wanting to spectate.
--
-- \param[in] resetAll
-- If true then the player is killed, their inventory destroyed, and their
-- score reset.
--
-- \param[in] norevive
-- If true then the player will not be revived (i.e. cannot respawn).
-- =============================================================================
function InstantAction.Server:OnChangeSpectatorMode(playerId, mode, targetId, resetAll, norevive)
    local player=System.GetEntity(playerId);
    if (not player) then
        return;
    end

    if (mode>0) then
        if(resetAll) then
            player.death_time=nil;
            player.inventory:Destroy();
            if(mode==1 or mode==2) then
                self.game:SetTeam(0, playerId);
            end
        end

        if(mode == 3) then
            if(targetId and targetId~=0) then
                local player = System.GetEntity(playerId);
                player.actor:SetSpectatorMode(3, targetId);
            else
                local newTargetId = self.game:GetNextSpectatorTarget(playerId, 1);
                if(newTargetId and newTargetId~=0) then
                    local player = System.GetEntity(playerId);
                    player.actor:SetSpectatorMode(3, newTargetId);
                else
                    mode = 1;
                    self.game:SetTeam(0, playerId);
                end
            end
        end
        if(mode == 1 or mode == 2) then
            local pos=g_Vectors.temp_v1;
            local angles=g_Vectors.temp_v2;

            player.actor:SetSpectatorMode(mode, NULL_ENTITY);
            local locationId=self.game:GetInterestingSpectatorLocation();
            if (locationId) then
                local location=System.GetEntity(locationId);
                if (location) then
                    pos=location:GetWorldPos(pos);
                    angles=location:GetWorldAngles(angles);

                    self.game:MovePlayer(playerId, pos, angles);
                end
            end
        end
    elseif (not norevive) then
        if (self:CanRevive(playerId)) then
            player.actor:SetSpectatorMode(0, NULL_ENTITY);

            self:RevivePlayer(player.actor:GetChannel(), player);
        end
    end

    if (resetAll) then
        --self:ResetScore(playerId); --DOn't reset scores just for going into spectator mode ffs
    end

    self.channelSpectatorMode[player.actor:GetChannel()]=mode;
end


-- =============================================================================
-- Function: InstantAction:OnUpdate
--
-- This function is called periodically to update various game parameters.
--
-- \param[in] frameTime
-- The current frame time of the game.
-- =============================================================================
function InstantAction.Server:OnUpdate(frameTime)
    self:UpdatePings(frameTime);
end


-- =============================================================================
-- Function: InstantAction:StartTicking
--
-- This function starts the ticking of the game timer. Note there is some debug
-- stuff here for SSM that needs to be updated if the line numbers in the file
-- ever change.
--
-- \param[in] client
-- The client object.
-- =============================================================================
function InstantAction:StartTicking(client)
    LogInfo("[AEGIS] InstantAction Injected StartTicking called");

    --
    -- TODO: Update these values with the correct line. Is this the best way we
    --       can do this??
    --
    local info = debug.getinfo(2);
    if ((info.short_src=="scripts/gamerules/instantaction.lua") and (info.currentline==242)) then
        LogInfo("[AEGIS] caller was original InstantAction.Server.OnStartGame");
        XScanServerLog();
        XResetConfig();
        XPrepareMap();
        XSafeCall(XCfgVar.OnNewMap, XVar.currentMapInfo.mapRules, XVar.currentMapInfo.mapName);
    elseif ((info.short_src=="scripts/gamerules/teaminstantaction.lua") and (info.currentline==839)) then
        LogInfo("[AEGIS] caller was original TeamInstantAction.Server.InGame.OnBeginState");
        XScanServerLog();
        XResetConfig();
        XPrepareMap();
        XSafeCall(XCfgVar.OnNewMap, XVar.currentMapInfo.mapRules, XVar.currentMapInfo.mapName);
    else
        LogInfo("[AEGIS] caller info: func=%s, currentlint=%d, short_src=%s",tostring(info.func), info.currentline, info.short_src);
    end

    --
    -- Kick off the timer.
    --
    if ((not client) or (not self.isServer)) then
        self:SetTimer(self.TICK_TIMERID, self.TICK_TIME);
    end
end


-- =============================================================================
-- Function: InstantAction.Server:OnTimer
--
-- This function is the server event handler for when a timer event takes place.
--
-- \param[in] timerId
-- The id of the timer that had the event.
--
-- \param[unused] msec
-- =============================================================================
function InstantAction.Server:OnTimer(timerId, msec)
    if (timerId==self.TICK_TIMERID) then
        if (self.OnTick) then
            self:OnTick();
            self:SetTimer(self.TICK_TIMERID, self.TICK_TIME);
        end
    elseif(timerId==self.NEXTLEVEL_TIMERID) then
        self:GotoState("Reset");
        self:XtendedNextLevel();
    end
end


-- =============================================================================
-- Function: InstantAction.Client:OnTimer
--
-- This function is the client event handler for when a timer event takes place.
--
-- \param[in] timerId
-- The id of the timer that had the event.
--
-- \param[unused] msec
-- =============================================================================
function InstantAction.Client:OnTimer(timerId, msec)
    if (timerId == self.TICK_TIMERID) then
        self:OnClientTick();
        if (not self.isServer) then
            self:SetTimer(self.TICK_TIMERID, self.TICK_TIME);
        end
    elseif (timerId == self.ENDGAME_TIMERID) then
        self:EndGame(true);
    elseif (timerId == self.FORCERESPAWN_TIMERID) then
		self.server:RequestRevive(g_localActorId);
    end
end


-- =============================================================================
-- Function: InstantAction.Client:OnUpdate
--
-- This function is called on the client when an update is to take place. It
-- currently updates the scores and the voice queue.
--
-- \param[in] frameTime
-- The current frame time of the game.
-- =============================================================================
function InstantAction.Client:OnUpdate(frameTime)
    SinglePlayer.Client.OnUpdate(self, frameTime);
    if(self.show_scores == true) then
        self:UpdateScores();
    end
    self:UpdateVoiceQueue(frameTime);
end


-- =============================================================================
-- Function: InstantAction:XtendedNextLevel
--
-- This function is called when the current game ends. It sets up the next level
-- to be played.
-- =============================================================================
-- TITAN
function InstantAction:XtendedNextLevel()
  if (XVar.mapthenactive) then
    XVar.mapthenactive = false;
    System.ExecuteCommand(XVar.mapthengamerules);
    System.ExecuteCommand(XVar.mapthenmaptime);
   if (XVar.mapthenscorelimit) then
    System.ExecuteCommand(XVar.mapthenscorelimit);
   end
   if (XVar.mapthendownloadlink) then
    System.ExecuteCommand(XVar.mapthendownloadlink);
   end
    System.ExecuteCommand(XVar.mapthenmap);
   return;
  end
    if (XCfgVar.useAlternateLevelRotation) then
        if (XCfgVar.mapForceAsNextLevel and XCfgVar.mapForceAsNextLevel~="") then
            LogAdmins("[AEGIS] next map being forced to %s", XCfgVar.mapForceAsNextLevel);
            System.ExecuteCommand("map "..XCfgVar.mapForceAsNextLevel);
            return;
        end
        if (XCfgVar.mapRepeatWhilePopulated and XVar.mapWasPopulated) then
            if (XVar.lastRecognizedLevelLoading) then
                if ((XVar.numberOfLevelRepeats>=XCfgVar.maxRepetitionsWhilePopulated) and (XCfgVar.maxRepetitionsWhilePopulated>0)) then
                    LogAdmins("[AEGIS] maximum number of repetitions reached now switching");
                else
                    LogAdmins("[AEGIS] repeat map: %s", XVar.lastRecognizedLevelLoading);
                    System.ExecuteCommand("map "..XVar.lastRecognizedLevelLoading);
                    return;
                end
            else
                LogAdmins("[AEGIS] unable to repeat map: loading message from log not found");
            end
        else
            if (XCfgVar.mapRepeatWhilePopulated) then
                LogAdmins("[AEGIS] repeating of populated maps id disabled.");
            else
                LogAdmins("[AEGIS] Map was not populated.");
            end
        end
        if (XCfgVar.mapPossibleAsNextLevel and #XCfgVar.mapPossibleAsNextLevel>0) then
            local possible = {};
            for i,map in ipairs(XCfgVar.mapPossibleAsNextLevel) do
                if (map ~= XVar.lastRecognizedLevelLoading) then
                    table.insert(possible,map);
                end
            end
            if (#possible>0) then
                local nextMap = possible[math.random(#possible)];
                LogAdmins("[AEGIS] next map selected from set of possible maps: %s", nextMap);
                System.ExecuteCommand("map "..nextMap);
                return;
            else
                LogAdmins("[AEGIS] set of possible maps only contains current map: %s", XVar.lastRecognizedLevelLoading);
                System.ExecuteCommand("map "..XVar.lastRecognizedLevelLoading);
                return;
            end
        end
    end
    LogAdmins("[AEGIS] next map selected by original level rotation mechanism");
    self.game:NextLevel();
end


-- =============================================================================
-- Function: InstantAction:UpdatePings
--
-- This function updates the pings for the clients but also has a lot of
-- processing for SSM features. It scans the server log, calls periodic tick
-- functions, checks for queued events, sends pending console messages,
-- and so forth.
--
-- \param[in] frameTime
-- The current frame time of the game.
-- =============================================================================
function InstantAction:UpdatePings(frameTime)
	XScanServerLog();
	if (XCfgVar.AEGISAntiCheat) then
	    XAEGISAntiCheat(frameTime);
	end
	if (XVar.currentMapInfo) then
		if (not XVar.tickTimer) then
			XVar.tickTimer = 0;
		end
		XVar.tickTimer = XVar.tickTimer - frameTime;
		if (XVar.tickTimer<=0) then

			XOnTimerTick();
			XVar.tickTimer = XVar.tickTimer + 1.0;
			if (XVar.tickTimer<=0) then
				XVar.tickTimer = 1.0;
			end
		end
	end

    -- executed queued calls
    while (#XVar.onUpdateQueue > 0) do
        local status, result = pcall(unpack(table.remove(XVar.onUpdateQueue,1)));
        if (not status) then
            LogAlways("[AEGIS] queued command failed: %s",tostring(result));
        end
    end

    -- send pending console messages (1 msg per frame, prevents bandwidth peaks)
    for i,player in ipairs(self.game:GetPlayers() or {}) do
        local queue = player.x_msg_queue_console;
        if (queue and (#queue>0)) then
            local msg = table.remove(queue, 1);
            g_gameRules.game:SendTextMessage(TextMessageConsole, msg, TextMessageToClient, player.id);
        end
    end


    -- handling delayed self terminations
    if (XCfgVar.useAutoMKCFeature) then
        -- if queue is larger than allowed it is considered mass kill
        local isMassKill = (#XVar.selfTerminationRequests>XCfgVar.autoMKCthreshold);
        local tmp = {};
        for i,req in ipairs(XVar.selfTerminationRequests) do
            if (isMassKill and not req.mkDetected) then
                LogAdmins("[AEGIS] Self-termination request for %s canceled due to possible MASS KILL",req.playerName)
                req.mkDetected = true;
            end
            local timeSinceRequested = _time-req.timeOfRequest;
            if (not req.mkDetected and not req.executed and
                (timeSinceRequested>XCfgVar.autoMKCdelay)) then
                LogAdmins("[AEGIS] Self-termination request for %s executed",req.playerName)
                req.executed = true;
                g_gameRules:KillPlayer(req.player);
            end
            if ((not req.executed and
                    (timeSinceRequested<=XCfgVar.autoMKChotTimeout))
                or (timeSinceRequested<=XCfgVar.autoMKCcoldTimeout)) then
                table.insert(tmp,req);
            end
        end
        XVar.selfTerminationRequests = tmp;
    end

    -- following original code
    if ((not self.pingUpdateTimer) or self.pingUpdateTimer>0) then
        self.pingUpdateTimer=(self.pingUpdateTimer or 0)-frameTime;
        if (self.pingUpdateTimer<=0) then
            local players = self.game:GetPlayers();

            if (players) then
                for i,player in ipairs(players) do
                    if (player and player.actor:GetChannel()) then
                        local ping=math.floor((self.game:GetPing(player.actor:GetChannel()) or 0)*1000+0.5);
                        self.game:SetSynchedEntityValue(player.id, self.SCORE_PING_KEY, ping);
                    end
                end
            end
            self.pingUpdateTimer=1;
        end
    end
end


-- =============================================================================
-- Function: InstantAction:ShowScores
--
-- This function is an accessor function that sets whether scores should be
-- shown or not.
--
-- \param[in] enable
-- True if scores should be shown.
-- =============================================================================
function InstantAction:ShowScores(enable)
    self.show_scores = enable;
end


-- =============================================================================
-- Function: InstantAction:EndGame
--
-- This function is called to end the game.
--
-- \param[in] enable
-- If true then the scoreboard and all input is frozen to force the player
-- immobile to see the scores.
-- =============================================================================
function InstantAction:EndGame(enable)
    self.force_scores=enable;
    self.show_scores=enable;
    self.game:ForceScoreboard(enable);
    self.game:FreezeInput(enable);
end


-- =============================================================================
-- Function: InstantAction:GetPlayerTeamKills
--
-- \param[unused] playerId
--
-- \return
-- This function always returns -1 since there are no teams in InstantAction.
-- =============================================================================
function InstantAction:GetPlayerTeamKills(playerId)
    return -1;
end


-- =============================================================================
-- Function: InstantAction:UpdateScores
--
-- This function updates the scores on the client machines.
-- =============================================================================
function InstantAction:UpdateScores()
    if (self.show_scores and g_localActor) then
        local players = Actor.GetActors();

        if (players) then
            --Send to C++
            g_localActor.actor:ResetScores();
            for i,player in ipairs(players) do
                local kills=self.game:GetSynchedEntityValue(player.id, self.SCORE_KILLS_KEY) or 0;
                local deaths=self.game:GetSynchedEntityValue(player.id, self.SCORE_DEATHS_KEY) or 0;
                local ping=self.game:GetSynchedEntityValue(player.id, self.SCORE_PING_KEY) or 0;
                local teamKills = self:GetPlayerTeamKills(player.id);

                g_localActor.actor:RenderScore(player.id, kills, deaths, ping, teamKills);

            end
        end
    end
end


-- =============================================================================
-- Function: InstantAction:SpawnPlayer
--
-- This function spawns a player after they join the server. This is only called
-- once! Respawn after a death is actually called "Revive".
--
-- SSM checks the player name here if the server is configured to sanitize names
-- or use funny names instead of Nomad.
--
-- \param[in] channelId
-- The id of the channel the player is connected on.
--
-- \param[in] name
-- The name of the player.
--
-- \return
-- The object for the player.
-- =============================================================================
function InstantAction:SpawnPlayer(channelId, name)
    if (not self.dudeCount) then
        self.dudeCount = 0;
    end;

    --
    -- Find the initial place to spawn the character entity. This is done at
    -- spectator points on the map.
    --
    local pos = g_Vectors.temp_v1;
    local angles = g_Vectors.temp_v2;
    ZeroVector(pos);
    ZeroVector(angles);

    local locationId=self.game:GetInterestingSpectatorLocation();
    if (locationId) then
        local location=System.GetEntity(locationId);
        if (location) then
            pos=location:GetWorldPos(pos);
            angles=location:GetWorldAngles(angles);
        end
    end

    --
    -- Check the players name to see if it needs to be sanitized or changed.
    --
    name = name or "Nomad";
    local saneName, isFunnyName = XSanitizePlayerName(name);
    if (saneName ~= name) then
        LogAdmins("[AEGIS] %q has been sanitized to %q on new connect.",
            string.gsub(name,"%$","$$"),
            string.gsub(saneName,"%$","$$"));
    end
    local player=self.game:SpawnPlayer(channelId, saneName, "Player", pos, angles);
    player.x_names = { {
        originalName = name,
        changedName = saneName,
        cause = "join",
    } };
    player.x_knownAs = saneName;
    if (isFunnyName) then
        player.x_funnyName = saneName;
    end
    return player;
end


-- =============================================================================
-- Function: InstantAction:SetupPlayer
--
-- This function sets up the ammo capacity of the player.
--
-- \param[in] player
-- The object of the player being setup.
-- =============================================================================
function InstantAction:SetupPlayer(player)
    player.ammoCapacity =
    {
        bullet            = 30 * 4,
        fybullet          = 30 * 4,
        lightbullet       = 20 * 8,
        smgbullet         = 20 * 8,
        explosivegrenade  = 3,
        flashbang         = 2,
        smokegrenade      = 1,
        empgrenade        = 1,
        scargrenade       = 3,
        rocket            = 3,
        sniperbullet      = 10 * 4,
        tacbullet         = 5  * 4,
        tagbullet         = 10,
        gaussbullet       = 5   * 4,
        hurricanebullet   = 500 * 2,
        incendiarybullet  = 30  * 4,
        shotgunshell      = 8   * 8,
        avexplosive       = 2,
        c4explosive       = 1,
        claymoreexplosive = 2,
        rubberbullet      = 30 * 4,
        tacgunprojectile  = 1,
        fgl40fraggrenade  = 6,
        fgl40empgrenade   = 6,
        aybullet          = 160,
    };

    if (player.inventory and player.ammoCapacity) then
        for ammo,capacity in pairs(player.ammoCapacity) do
            player.inventory:SetAmmoCapacity(ammo, capacity);
        end
    end
end


-- =============================================================================
-- Function: InstantAction:IsSpawnSafe
--
-- This function determines if the spawn location is safe for a player.
--
-- \param[in] player
-- The object of the player.
--
-- \param[unused] spawn
--
-- \param[in] safedist
-- The distance to be checked.
-- =============================================================================
function InstantAction:IsSpawnSafe(player, spawn, safedist)
    local teamId=self.game:GetTeam(player.id);
    local entities=System.GetPhysicalEntitiesInBoxByClass(spawn:GetWorldPos(g_Vectors.temp_v1), safedist, "Player");
    if (entities) then
        for i,v in pairs(entities) do
            if (not v:IsDead() and player~=v) then
                if (teamId==0 or teamId~=self.game:GetTeam(v.id)) then
                    return false;
                end
            end
        end
    end

    return true;
end


-- =============================================================================
-- Function: InstantAction:RequestSpawnGroup
--
-- This function requests a spawn group for the local actor.
--
-- \param[in] groupId
-- The id of the spawn group that is being requested.
-- =============================================================================
function InstantAction:RequestSpawnGroup(groupId)
    self.server:RequestSpawnGroup(g_localActorId, groupId);
end


-- =============================================================================
-- Function: InstantAction:SetPlayerSpawnGroup
--
-- This function sets the spawn group for the player.
--
-- \param[in] playerId
-- The id of the player in question.
--
-- \param[in] spawnGroupId
-- The id of the spawn group the player is to be set to.
-- =============================================================================
function InstantAction:SetPlayerSpawnGroup(playerId, spawnGroupId)
    local player=System.GetEntity(playerId);
    if (player) then
        player.spawnGroupId=spawnGroupId;
    end
end


-- =============================================================================
-- Function: InstantAction:GetPlayerSpawnGroup
--
-- \param[in] player
-- If object of the player in question.
--
-- \return
-- This function returns the spawn group id of the player.
-- =============================================================================
function InstantAction:GetPlayerSpawnGroup(player)
    return player.spawnGroupId or NULL_ENTITY;
end


-- =============================================================================
-- Function: InstantAction:CanRevive
--
-- \param[in] playerId
-- The id of the player in question.
--
-- \return
-- True if the player can revive or false if not.
-- =============================================================================
function InstantAction:CanRevive(playerId)
    local player=System.GetEntity(playerId);
    if (not player) then
        return false;
    end

    local groupId=player.spawnGroupId;
    if ((not self.USE_SPAWN_GROUPS) or (groupId and groupId~=NULL_ENTITY)) then
        return true;
    end
    return false;
end


-- =============================================================================
-- Function: InstantAction:RevivePlayer
--
-- This function revives the player (i.e. spawns the player).
--
-- \param[in] channelId
-- The id of the channel the player is connected on.
--
-- \param[in] player
-- The object of the player to be revived.
--
-- \param[in] keepEquip
--
-- =============================================================================
function InstantAction:RevivePlayer(channelId, player, keepEquip)
    local result=false;
    local groupId=player.spawnGroupId;
    local teamId=self.game:GetTeam(player.id);

    if (player:IsDead()) then
        keepEquip=false;
    end

    player.lastExitedVehicleId = nil;
    player.lastExitedVehicleTime = nil;
    if (self.USE_SPAWN_GROUPS and groupId and groupId~=NULL_ENTITY) then
        local spawnGroup=System.GetEntity(groupId);
        if (spawnGroup and spawnGroup.vehicle) then -- spawn group is a vehicle, and the vehicle has some free seats then
            result=false;
            for i,seat in pairs(spawnGroup.Seats) do
                if ((not seat.seat:IsDriver()) and (not seat.seat:IsGunner()) and (not spawnGroup.x_lock) and (seat.seat:IsFree()))  then
                    self.game:RevivePlayerInVehicle(player.id, spawnGroup.id, i, teamId, not keepEquip);
                    result=true;
                    break;
                end
            end

            -- if we didn't find a valid seat, rather than failing pass an invalid seat id. RevivePlayerInVehicle will try and
            --  find a respawn point at one of the seat exits etc.
            if(not result) then
                self.game:RevivePlayerInVehicle(player.id, spawnGroup.id, -1, teamId, not keepEquip);
                result=true;
            end
        end
    elseif (self.USE_SPAWN_GROUPS) then
        Log("Failed to spawn %s! teamId: %d  groupId: %s  groupTeamId: %d", player:GetName(), self.game:GetTeam(player.id), tostring(groupId), self.game:GetTeam(groupId or NULL_ENTITY));

        return false;
    end

    if (not result) then
        local ignoreTeam=(groupId~=nil) or (not self.TEAM_SPAWN_LOCATIONS);

        local includeNeutral=true;
        if (self.TEAM_SPAWN_LOCATIONS) then
            includeNeutral=self.NEUTRAL_SPAWN_LOCATIONS or false;
        end

        local spawnId,zoffset;
        if (self.TIA_SPAWN_LOCATIONS) then
            spawnId,zoffset = self.game:GetSpawnLocationTeam(player.id, player.death_pos);
        else
            if(player.spawn_time==nil or _time-player.spawn_time>100) then
                player.skipSpawnId=0;
            end
        if (self.USE_SPAWN_GROUPS or (not player.death_time) or (not player.death_pos)) then
                spawnId,zoffset = self.game:GetSpawnLocation(player.id, ignoreTeam, includeNeutral, groupId or NULL_ENTITY, 0, g_Vectors.v000, player.skipSpawnId);
        else
                spawnId,zoffset = self.game:GetSpawnLocation(player.id, ignoreTeam, includeNeutral, groupId or NULL_ENTITY, 50, player.death_pos, player.skipSpawnId);
            end
            player.spawn_time=_time;
            player.skipSpawnId=spawnId;
        end

        local pos,angles;

        if (spawnId) then
            local spawn=System.GetEntity(spawnId)
            if (spawn) then
                spawn:Spawned(player);
                pos=spawn:GetWorldPos(g_Vectors.temp_v1);
                angles=spawn:GetWorldAngles(g_Vectors.temp_v2);
                pos.z=pos.z+zoffset;

                if (zoffset>0) then
                    Log("Spawning player '%s' with ZOffset: %g!", player:GetName(), zoffset);
                end

                self.game:RevivePlayer(player.id, pos, angles, teamId, not keepEquip);

                result=true;
            end
        end
    end

    -- make the game realise the areas we're in right now...
    -- otherwise we'd have to wait for an entity system update, next frame
    player:UpdateAreas();

    if (result) then
        if(player.actor:GetSpectatorMode() ~= 0) then
            player.actor:SetSpectatorMode(0, NULL_ENTITY);
        end

        if (not keepEquip) then
            local additionalEquip;
            if (groupId) then
                local group=System.GetEntity(groupId);
                if (group and group.GetAdditionalEquipmentPack) then
                    additionalEquip=group:GetAdditionalEquipmentPack();
                end
            end
            self:EquipPlayer(player, additionalEquip);
        end
        player.death_time=nil;
        player.frostShooterId=nil;

        local invuln = System.GetCVar("g_spawnProtectionTime");
        if (invuln and invuln>0) then
            self.game:SetInvulnerability(player.id, true, invuln);
            player.x_startInvulnerabilityTime = _time;
        end
        player.x_lastActorStats = nil;
    end

    if (not result) then
        Log("Failed to spawn %s! teamId: %d  groupId: %s  groupTeamId: %d", player:GetName(), self.game:GetTeam(player.id), tostring(groupId), self.game:GetTeam(groupId or NULL_ENTITY));
    end

    return result;
end


-- =============================================================================
-- Function: InstantAction:OnEnterVehicleSeat
--
-- This function is called when a player enters a seat. As of right now all this
-- function does is give the entering entity a parachute if the vehicle is an
-- air vehicle.
--
-- \param[in] vehicle
-- The object of the vehicle containing the seat being entered.
--
-- \param[in] seat
-- The object of the seat being entered.
--
-- \param[in] entityId
-- The id of the entity (player or AI) entering the seat.
-- =============================================================================
function InstantAction:OnEnterVehicleSeat(vehicle, seat, entityId)
    if(self.isServer) then
        if (vehicle.vehicle:GetMovementType()=="air") then
            local player=System.GetEntity(entityId);
            if (player) then
                if (player.inventory:GetCountOfClass("Parachute")==0) then
                    ItemSystem.GiveItem("Parachute", entityId, false);
                end
            end
        end
    end
end


-- =============================================================================
-- Function: InstantAction:OnLeaveVehicleSeat
--
-- This function is called when a player leaves a vehicle seat. It simply sets
-- the vehicle id and exit time to the player if the player is exiting the
-- vehicle.
--
-- \param[in] vehicle
-- The object of the vehicle being exited.
--
-- \param[in] seat
-- The object of the seat being exited.
--
-- \param[in] passengerId
-- The id of the player leaving the seat.
--
-- \param[in] exiting
-- True if the player is exiting the vehicle and not just changing seats.
-- =============================================================================
function InstantAction:OnLeaveVehicleSeat(vehicle, seat, passengerId, exiting)
    if(self.isServer) then
        if(exiting) then
            local player=System.GetEntity(passengerId);
            if(player) then
                player.lastExitedVehicleId = vehicle.id;
                player.lastExitedVehicleTime = _time;
            end
        end
    end
end


-- =============================================================================
-- Function: InstantAction:GetEnergyAbsorptionValue
--
-- \param[unused] player
--
-- \return
-- This function returns the amount of damage one point of energy absorbs. It is
-- hard coded to always return 1/1.5.
-- =============================================================================
function InstantAction:GetEnergyAbsorptionValue(player)
    return 1/1.5;
end


-- =============================================================================
-- Function: InstantAction:GetDamageAbsorption
--
-- This function determines the amount of damage absorption based on the energy
-- level of the nanosuit. Note that the suit only absorbs damage when in the
-- armor mode (mode 3).
--
-- \param[in] player
-- The object of the player.
--
-- \param[in] hit
-- Data structure containing all the information about the hit.
--
-- \return
-- A percentage value between 0 and 1 specifying how much damage was absorbed.
-- Note that a value of 1 means ALL the damage was absorbed.
-- =============================================================================
function InstantAction:GetDamageAbsorption(player, hit)
    --
    -- If there is no damage being done or the damage type is from an admin
    -- punish command then return 0; no damage is being absorbed.
    --
    if (hit.damage == 0.0 or hit.type=="punish") then
        return 0;
    end;

    --
    -- Damage is only absorbed by the nanosuit when in the armor mode. If the
    -- suit is in armor mode then compute the amount of damage absorption.
    --
    local nanoSuitMode = player.actor:GetNanoSuitMode();
    if(nanoSuitMode == 3) then
        local currentSuitEnergy = player.actor:GetNanoSuitEnergy();
        --
        -- Reduce energy based on damage. The left over will be reduced from the
        -- health. Note that armor energy is 25% weaker than health.
        --
        local suitEnergyLeft = currentSuitEnergy - (hit.damage*4.2);
        local absorption = 0.0;
        if (suitEnergyLeft < 0.0) then
            --
            -- There was not enough suit energy to absorb the entire hit.
            -- Compute the percentage value (0,1) of damage absorption.
            --
            player.actor:SetNanoSuitEnergy(0);
            absorption = 1 + suitEnergyLeft/(hit.damage*4.2);
        else
            --
            -- There was enough suit energy to absorb all the damage from the
            -- hit. Set the new suit energy value and return 1 to indicate that
            -- 100% of the damage was absorbed.
            --
            player.actor:SetNanoSuitEnergy(suitEnergyLeft);
            absorption = 1;
        end

        return math.max(0, absorption);
    end

    return 0;
end


-- =============================================================================
-- Function: InstantAction:ProcessActorDamage
--
-- This function processes any damage done to a player.
--
-- \param[in] hit
-- Data structure containing all the information about the hit.
--
-- \return
-- True if the hit makes the targets health less than or equal to 0.
-- =============================================================================
function InstantAction:ProcessActorDamage(hit)
    local targetType,
          targetComponent,
          weaponClass,
          weaponHitType,
          weaponDamageOriginal,
          weaponDamageModded,
          weaponDamageMultiplier,
          isHackOnLookup = XGetWeaponDamageInfo(hit);
    local isHackDetected = false;
    local hitDumped = true;
    if (isHackOnLookup) then
        XHitDump(hit,"==== lookup for "..weaponClass.."_"..weaponHitType);
        hitDumped = true;
        isHackDetected = true;
    elseif ((weaponClass=="unknown") and (weaponHitType=="other") and hit.material_type) then
        XHitDump(hit,"==== unknown/other on body part instead of N/A");
        hitDumped = true;
        isHackDetected = true;
    elseif ((weaponClass=="unknown") and (weaponHitType=="other") and (hit.damage==8190)) then
        XHitDump(hit,"==== unknown/other/N/A with damage of 8190");
        hitDumped = true;
        isHackDetected = true;
    elseif ((weaponClass=="Player") and (weaponHitType=="other") and (hit.damage==8190) and (((hit.target == hit.shooter) or (not hit.shooter)) and (hit.target ~= hit.weapon))) then
        XHitDump(hit,"==== Player/other with damage of 8190 and the cheater as weapon LOL");
        hitDumped = true;
        isHackDetected = true;
        hackername = hit.weapon:GetName();
        XLogHackerDetails(hit.weapon, "Mass kill");
        XDealWithHacker(hit.weapon, "Mass kill");

    elseif (hit.damage > 1000) then
        -- for now all hits with damage > 1000 are suspicuous and
        -- to get some data on them we do a full dump into the log
        -- this will give us much better data next time a cheater gets caught
        XHitDump(hit,"==== damage>1000");
        hitDumped = true;
    end;
    local shotInc = 0;
    local matDmgMult = 1;
    if (hit.material_type and (self.DamagePlayerToPlayer[hit.material_type]~=nil)) then
        matDmgMult = self.DamagePlayerToPlayer[hit.material_type];
        shotInc = 1; -- only these shots can be evaluated in statistics
    end
    local matDamage = math.ceil(matDmgMult*weaponDamageOriginal);
    local target=hit.target;
    local targetName = "N/A";
    local targetTeamId = 0;
    if (hit.target) then
        targetName = target:GetName();
        targetTeamId = g_gameRules.game:GetTeam(target.id) or 0;
    end
    -- 
    -- An admin is always invulnerable. For non-admins, we check with the game
    -- to see if the player is currently invulnerable. This is done for things
    -- like when a player is revived they are invulnerable for a certain number
    -- of seconds.
    --
	local isInvulnerable = g_gameRules.game:IsInvulnerable(target.id);
    if (XIsProtected(target)) then
        g_gameRules.game:SetInvulnerability(target.id, true, g_gameRules.INVULNERABILITY_TIME);
        isInvulnerable = g_gameRules.game:IsInvulnerable(target.id);
	end


        
    --
    -- If SSM is configured to add additional invulnerability time then extend
    -- the amount of time here for the player.
    --
    if ((not isInvulnerable) and XCfgVar.forceFullInvulnerabilityTime) then
        local startInvulnerabilityTime = target.x_startInvulnerabilityTime or 0;
        isInvulnerable = (_time - startInvulnerabilityTime) <= self.INVULNERABILITY_TIME;
    end
    --
    -- If the player is invulnerable then the hit is never counted.
    --
    if (isInvulnerable) then
        shotInc = 0;
    end
    --
    -- Never count hits while the server is in MKC mode.
    --
    if (XRuntimeVar.isMassKillCountermeasureEnabled) then
        shotInc = 0;
    end
    --
    -- If the server is configured to use modified weapon damage modifiers then
    -- set the new modifier.
    --
    local dmgModifier = 1.0;
    if (XCfgVar.useModifiedWeaponDamageToPlayerFeature) then
        dmgModifier = weaponDamageMultiplier;
    end
    --
    -- Never count hits when there is an imposter involved.
    --
    local hint = "";
    if (XCfgVar.useAdminImposterProtectionFeature) then
        if (hit.shooter and XIsImposter(hit.shooter)) then
            dmgModifier = 0;
            hint = "IMPOSTER";
            shotInc = 0;
        end
        if (XIsImposter(target)) then
            dmgModifier = 25;
            hint = "IMPOSTER";
            shotInc = 0;
        end
    end
    --
    -- Gather statistics on the shooter.
    --
    local shooterName = "N/A";
    local shooterHits = 0;
    local shooterHeadShots = 0;
    local shooterTeamId = 0;
    if (hit.shooter) then
        shooterName = hit.shooter:GetName();
        shooterTeamId = g_gameRules.game:GetTeam(hit.shooter.id) or 0;
        if (not hit.shooter.x_cntHits) then
            hit.shooter.x_cntHits = 0;
            hit.shooter.x_cntHeadShots = 0;
        end
        hit.shooter.x_cntHits = hit.shooter.x_cntHits + shotInc;
        if (hit.material_type=="head") then
            hit.shooter.x_cntHeadShots = hit.shooter.x_cntHeadShots + shotInc;
        end
        shooterHits = hit.shooter.x_cntHits;
        shooterHeadShots = hit.shooter.x_cntHeadShots;
    end
    --
    -- Log the hits whether they are suspected or OK.
    --
    local color1 = X_CONSOLE_COLOR.grey;
    local color2 = X_CONSOLE_COLOR.grey;
    local comment = "    ";
    local isOK = true;
    local isKillShot = false;
    if (shotInc>0) then
        if ((matDamage>=0) and (hit.damage>matDamage)) then
            color1 = X_CONSOLE_COLOR.yellow;
            comment = "SUSP";
            isOK = false;
        else
            color1 = X_CONSOLE_COLOR.green;
            comment = "_OK_";
        end
    end
    
    if (shooterHits==0) then
        shooterHits = 1;
    end
    
    local hitDamageClient = hit.damage;
    hit.damage = math.floor(hit.damage * dmgModifier);
    
    local health=target.actor:GetHealth();
    local healthBefore = health;

    local absorbed = 1;
    local dmgEffective = hit.damage;
    if (isInvulnerable) then
        --
        -- The target is invulnerable so tell the shooter such.
        --
        absorbed = 1;
        hint = " INVULNERABLE";
        color2 = X_CONSOLE_COLOR.yellow;
        dmgEffective = 0;
        if (XIsProtected(target) and hit.shooter and (hit.shooter~=target)) then
            XSendCenterMessageToPlayer(hit.shooter, XCfgVar.adminProtectMsg, target:GetName());
            XSendServerMessageToPlayer(hit.shooter, XCfgVar.adminProtectMsg, target:GetName());
        end
    else
        --
        -- Check for MKC hack and ignore any self-termination requests during
        -- MKC enabled mode.
        --
        local isMKCenabled = XRuntimeVar.isMassKillCountermeasureEnabled;
        if (XCfgVar.useAutoMKCFeature) then
            if (not isHackDetected and (weaponClass=="Player") and (weaponHitType=="other") and (hitDamageClient==8190)) then
                --
                -- A self termination event is recognized so disable direct
                -- damage from it. Note that no action is needed if the player
                -- is in the penalty box since suicides are ignored from them.
                --
                isMKCenabled = true;
                
				local profile = XGetIdentityValue(target, "profile");
				if (profile~="0") then
					if (AEGIS.PlayerInformationByProfile[profile] and AEGIS.PlayerInformationByProfile[profile].PenaltyBox) then
	                	    LogAdmins("[AEGIS] Self-termination request for %s ignored: PENALTY BOX",targetName);
	                else
	                    --
	                    -- Replace the self termination event with a delayed self
	                    -- termination event that will be handled once we are out of
	                    -- MKC enabled mode.
	                    --
	                    local mkInProgress = false;
	                    local isInQueue = false;
	                    for i,req in ipairs(XVar.selfTerminationRequests) do
	                        if (req.mkDetected) then
	                            mkInProgress = true;
	                        end
	                        if (req.player == target) then
	                            isInQueue = true;
	                        end
	                    end
	                    if (mkInProgress and not hitDumped) then
	                        XHitDump(hit,string.format("==== MK in progress (orig. damage = %d",hitDamageClient));
	                        hitDumped = true;
	                    end
	                    if (not isInQueue) then
	                        --
	                        -- Only queue a player once.
	                        --
	                        table.insert(XVar.selfTerminationRequests,{
	                            player = target;
	                            playerName = targetName;
	                            timeOfRequest = _time;
	                            mkDetected = mkInProgress;
	                        });
	                        if (mkInProgress) then
	                            LogAdmins("[AEGIS] Self-termination request for %s queued during MASS KILL",targetName);
	                        else
	                            LogAdmins("[AEGIS] Self-termination request for %s queued for execution",targetName);
	                        end
	                    else
	                        LogAdmins("[AEGIS] Self-termination request for %s not queued: duplicate",targetName);
	                    end
	                end
				end
            end
        end
        
        --
        -- If no hack was detected and MKC is not enabled then compute the new
        -- health of the target based on the modified hit damage and the amount
        -- of damage absorption.
        --
        if (not isHackDetected and not isMKCenabled) then
            health = math.floor(health - hit.damage*(1-self:GetDamageAbsorption(target, hit)));
        end
        
        dmgEffective = healthBefore - health;

        --
        -- Set the targets new health and if it was a kill shot then check for
        -- admin imposters and handle them if present.
        --
        target.actor:SetHealth(health);
        if (health<=0) then
            hint = " DEAD";
            isKillShot = true;
            color2 = X_CONSOLE_COLOR.orange;
            if (XCfgVar.useAdminImposterProtectionFeature) and (XIsImposter(target)) then
                XSendCenterMessageToPlayer(target, XCfgVar.adminImposterWarning, XCfgVar.adminTag);
                XSendServerMessageToPlayer(target, XCfgVar.adminImposterWarning, XCfgVar.adminTag);
                target.x_imposterWarningSent = _time;
            end
        end
    end

    --
    -- Set the log colors to red if a hack is detected on this hit.
    --
    if (isHackDetected) then
        color1 = X_CONSOLE_COLOR.red;
        comment = "HACK";
        color2 = X_CONSOLE_COLOR.red;
        isOK = false;
        XDealWithHacker(hit.shooter, "HIT HACK");
    end

    if (XCfgVar.useHitDebugFeature) then
        if (not XCfgVar.skipAutoTurretHits or ((weaponClass~="AutoTurret") and (weaponClass~="AutoTurretAA"))) then
            local stats = "";
            if (shotInc>0) then
                stats = XFormat(" %2d%%/%4d",
                    math.floor(100*shooterHeadShots/shooterHits),
                    shooterHits
                );
            end
            SendHit(hit.shooter, hit.target,
                XFormat("[HIT] %s %2d-%3d%%=%4d %s/%s/%s %s => %s (HP:%3d)%s%s",
                    color1..comment..color2,
                    hit.damage,
                    math.floor(100*absorbed),
                    dmgEffective,
                    weaponClass,
                    weaponHitType,
                    (hit.material_type or "N/A"),
                    X_TEAM_COLOR[shooterTeamId]..shooterName..color2,
                    X_TEAM_COLOR[targetTeamId]..targetName..color2,
                    health,
                    hint,
                    stats
                ),
                XFormat("[HIT] %2d-%3d%%=%4d %s/%s/%s %s => %s (HP:%3d)%s",
                    hit.damage,
                    math.floor(100*absorbed),
                    dmgEffective,
                    weaponClass,
                    weaponHitType,
                    (hit.material_type or "N/A"),
                    shooterName,
                    targetName,
                    health,
                    hint
                ),
                isOK,
                isKillShot
            );
            LogHit("[HIT] %s %3d*%4.2f=%4d>=%4d*%4.2f=%3d-%3d%%=%4d %s/%s/%s %s => %s (HP:%3d)%s%s",
                comment,
                weaponDamageOriginal,
                matDmgMult,
                matDamage,
                hitDamageClient,
                dmgModifier,
                hit.damage,
                math.floor(100*absorbed),
                dmgEffective,
                weaponClass,
                weaponHitType,
                (hit.material_type or "N/A"),
                shooterName,
                targetName,
                health,
                hint,
                stats
            );
            if (XHitDebug) then
                XSafeCall(XHitDebug, hit); -- makes sure that exceptions wont propagate
            end
        end
    end
    --Hit Info
	XSafeCall(PrintHitInfo, hit)
    return (health <= 0);
end

function PrintHitInfo(hit)
	if ((hit.target and hit.shooter) and (hit.shooter~=hit.target)) then
  		local SuitModeLabel = {[0] = "SPEED   ";[1] = "STRENGTH";[2] = "CLOAK   ";[3] = "ARMOR   ";};
  		-- info for the target
		local VehicleId = hit.shooter.actor:GetLinkedVehicleId()
		if (VehicleId) then
		    local Vehicle = System.GetEntity(VehicleId);
		    local vehicleName = "unknown";
		    if (Vehicle) then
		        vehicleName = Vehicle.class
		        local vehicleHealth = math.floor((Vehicle.vehicle:GetRepairableDamage() * 100))
		        XSendBigCenterMessageToPlayer(hit.target, "Attacker: "..hit.shooter:GetName().." in "..vehicleName.." with "..vehicleHealth.." hp")
			end
  		else
  		    local lHealth = hit.shooter.actor:GetHealth()
  			if (lHealth<1) then
  		    	lHealth = "Rip"
			end
  		    XSendBigCenterMessageToPlayer(hit.target,"Attacker: "..hit.shooter:GetName().." Energy: "..math.floor(hit.shooter.actor:GetNanoSuitEnergy()/2).." Mode: "..SuitModeLabel[hit.shooter.actor:GetNanoSuitMode()].." Health: "..lHealth)
		end
		-- info for the shooter
  		local VehicleId = hit.target.actor:GetLinkedVehicleId()
        if (VehicleId) then
		    local Vehicle = System.GetEntity(VehicleId);
		    local vehicleName = "unknown";
		    if (Vehicle) then
		        vehicleName = Vehicle.class
		        local vehicleHealth = math.floor((Vehicle.vehicle:GetRepairableDamage() * 100))
		        XSendBigCenterMessageToPlayer(hit.shooter, "Target: "..hit.target:GetName().." in "..vehicleName.." with "..vehicleHealth.." hp")
			end
  		else
  			local lHealth = hit.target.actor:GetHealth()
  			if (lHealth<1) then
  		    	lHealth = "Rip"
			end
  		    XSendBigCenterMessageToPlayer(hit.shooter,"Target: "..hit.target:GetName().." Energy: "..math.floor(hit.target.actor:GetNanoSuitEnergy()/2).." Mode: "..SuitModeLabel[hit.target.actor:GetNanoSuitMode()].." Health: "..lHealth)
		end
	end
end
-- =============================================================================
-- Function: InstantAction:CalcExplosionDamage
--
-- This function calculates the damage done by an explosive device.
--
-- \param[in] entity
-- The object of the entity hit by the explosion.
--
-- \param[in] explosion
-- The object of the explosion.
--
-- \param[in] obstruction
--
-- \return
-- The damage amount dealt by the explosion.
-- =============================================================================
function InstantAction:CalcExplosionDamage(entity, explosion, obstruction)
    local newDamage = SinglePlayer.CalcExplosionDamage(self, entity, explosion, obstruction);

    --
    -- Claymore explosions are directional and the damage depends on angle of approach.
    --
    if(explosion.effectClass == "claymoreexplosive") then
        local explosionPos = explosion.pos;
        local entityPos = entity:GetWorldPos();

        local edir = vecNormalize(vecSub(entityPos, explosionPos));
        local dot = 1;
        if (edir) then
            dot = vecDot(edir, explosion.dir);
        end

        if(dot > 0.0) then
            newDamage = (0.1 * newDamage) + (0.9 * newDamage * dot);
        else
            newDamage = 0.1 * newDamage;
        end

        local debugHits = self.game:DebugHits();
        if (debugHits>0) then
            local angle = math.abs(math.acos(dot));
            Log("%s hit by claymore: dot %f, angle %f, damage %f", entity:GetName(), dot, angle, newDamage);
        end
    end

    return newDamage;
end


-- =============================================================================
-- Function: InstantAction.Server:OnFreeze
--
-- This function is called when a player is frozen. It checks to see if the
-- freeze is allowed or not based on the freeze hack check and the game check.
--
-- \param[in] targetId
-- The id of the player that was frozen.
--
-- \param[in] shooterId
-- The id of the player doing the freezing.
--
-- \param[in] weaponId
-- The id of the weapon that the shooter used on the target.
--
-- \param[in] value
--
-- \return
-- True if the freeze is sucessful; false if not.
-- =============================================================================
function InstantAction.Server:OnFreeze(targetId, shooterId, weaponId, value)
    local target=System.GetEntity(targetId);

    if(XCfgVar.useFreezeHackDetect) then
        if(weaponId and shooterId) then
            local weapon = System.GetEntity(weaponId);
            local shooter = System.GetEntity(shooterId);
            if(weapon and shooter) then
                --
                -- If the weapon that the shooter is using is not permitted to
                -- freeze a target then a freeze hack is being performed. Log
                -- the hack event and kill the cheater if the server is
                -- configured to do so.
                --
                if(not XCfgVar.weaponPermitFreeze[weapon.class]) then
        			XLogHackerDetails(shooter, "Freeze hack");
        			XDealWithHacker(shooter, "Freeze hack");
                    return false;
                end
            end
        end
    end

    if (target.OnFreeze and not target:OnFreeze(shooterId, weaponId, value)) then
        return false;
    end

    if ( not XCfgVar.allowWeaponsToFreezeVehicles ) then
        if ( target.actor ) then
            target.frostShooterId=shooterId;
        end
    else
        if (target.actor or target.vehicle) then
            target.frostShooterId=shooterId;
        end
    end
    
    return true;
end


-- =============================================================================
-- Function: InstantAction:OnPlayerKilled
--
-- This function is called when a player is killed.
--
-- \param[in] hit
-- Data structure containing all the information about a hit.
-- =============================================================================
function InstantAction.Server:OnPlayerKilled(hit)
    local target=hit.target;
    target.death_time=_time;
    target.death_pos=target:GetWorldPos(target.death_pos);

    self.game:KillPlayer(hit.targetId, true, true, hit.shooterId, hit.weaponId, hit.damage, hit.materialId, hit.typeId, hit.dir);
    self:ProcessScores(hit);
    
    local vehicleId=target.actor:GetLinkedVehicleId();
    if (vehicleId) then
        local vehicle=System.GetEntity(vehicleId);
        if (vehicle) then
            vehicle.x_lock = nil;
        end
    end
    XKillStreak(hit.shooter, hit.target);
    System.LogAlways("Calling info NOW")
    --XSafeCall(XLiveInfo, hit.shooter, hit.target);
end


-- =============================================================================
-- Function: InstantAction:CalculateScore
--
-- This function calculates the score of a player. The player will get one point
-- for every kill and -1 point for every death.
--
-- \param[in] deaths
-- The number of deaths the player has.
--
-- \param[in] kills
-- The number of kills the player has.
--
-- \param[unused] teamkills
--
-- \return
-- Returns the computed score.
-- =============================================================================
function InstantAction:CalculateScore(deaths, kills, teamkills)
    local score = (deaths * -1) + (kills * 1);
    return score;
end


-- =============================================================================
-- Function: InstantAction:DisplayKillScores
--
-- \return
-- This function always returns true.
-- =============================================================================
function InstantAction:DisplayKillScores()
    return true;
end


-- =============================================================================
-- Function: InstantAction.Client:OnKill
--
-- This function is called on the client when a kill event takes place.
--
-- \param[in] playerId
-- The id of the player.
--
-- \param[in] shooterId
-- The id of the shooter.
--
-- \param[in] weaponClassName
-- The weapon class name of the weapon used to kill.
--
-- \param[in] damage
-- The amount of damage done by the weapon.
--
-- \param[in] material
-- Used to find where the target was shot.
--
-- \param[in] hit_type
-- Used to find the type of attack that was performed.
-- =============================================================================
function InstantAction.Client:OnKill(playerId, shooterId, weaponClassName, damage, material, hit_type)
    local matName=self.game:GetHitMaterialName(material) or "";
    local type=self.game:GetHitType(hit_type) or "";

    local headshot=string.find(matName, "head");
    local melee=string.find(type, "melee");

    --
    -- Apply the proper death effect based on the type of attack that was
    -- performed on the player.
    --
    if(playerId == g_localActorId) then
        if(headshot) then
            HUD.ShowDeathFX(2);
        elseif (melee) then
            HUD.ShowDeathFX(3);
        else
            HUD.ShowDeathFX(1);
        end
    end
    
    local points = 1;
    if (playerId==g_localActorId) then
        --
        -- The player was the one killed. Calculate the players new score based
        -- on wether he was killed by a team mate or an enemy.
        --
        if(self.game:GetTeamCount()>1 and self.game:GetTeam(shooterId)==self.game:GetTeam(playerId) and shooterId~=playerId) then
            points = self:CalculateScore(0, 0, 0, 0);
        else
            points = self:CalculateScore(1, 0, 0, 0);
        end
    elseif (shooterId==g_localActorId) then
        --
        -- The player was the one doing the killing. Calculate the players new
        -- score based on whether he killed a team mate, an enemy, or himself.
        --
        if(playerId==shooterId) then
            points = self:CalculateScore(0, 0, 0, 1);
        else
            if(self.game:GetTeamCount()>1 and self.game:GetTeam(shooterId)==self.game:GetTeam(playerId)) then
                points = self:CalculateScore(0, 0, 1, 0);
            else
                points = self:CalculateScore(0, 1, 0, 0);
            end
        end
    end
    local target=System.GetEntity(playerId);
    local shooter=System.GetEntity(shooterId);

    --
    -- If the player was killed then keep a table of the other players that
    -- have killed him. This is used to track the number of times each player
    -- has killed the local player. Also display any messages relating to his
    -- death.
    --
    if(playerId==g_localActorId) then
        if (shooter and shooter.actor and shooter.actor:IsPlayer()) then
            if(target.myKillersCountTable==nil) then
                target.myKillersCountTable = {};
            end
            if(target.myKillersCountTable[shooter.id]== nil) then
                target.myKillersCountTable[shooter.id] = 0;
            end
            local teamkill = (self.game:GetTeam(playerId)==self.game:GetTeam(shooterId)) and (self.game:GetTeamCount() > 1);
            target.myKillersCountTable[shooter.id] = target.myKillersCountTable[shooter.id]+1;
            HUD.DisplayKillMessage(shooter:GetName(), target.myKillersCountTable[shooter.id], teamkill, false, playerId==shooterId, points);
            if (points~=0 and self:DisplayKillScores()) then
                HUD.DisplayFunMessage(tostring(points));
            end
        end
    end
    
    --
    -- Send big message to shooter if hit is a headshot. Added SSM AEGIS 1.2.
    --
    if (headshot and XCfgVar.sendMsgOnHeadshot) then
        XSendBigCenterMessageToPlayer(shooter,XFormat(XCfgVar.onHeadshotMsg, target:GetName()));
    end

    --
    -- If the player did the killing them update the table that stores his kills
    -- and display messages relating to his new score.
    --
    if(shooterId==g_localActorId and playerId~=shooterId) then
        if (target and target.actor and target.actor:IsPlayer()) then
            if(shooter.myKillsCountTable==nil) then
                shooter.myKillsCountTable = {};
            end
            if(shooter.myKillsCountTable[target.id]== nil) then
                shooter.myKillsCountTable[target.id] = 0;
            end
            local teamkill = (self.game:GetTeam(playerId)==self.game:GetTeam(shooterId)) and (self.game:GetTeamCount() > 1);
            shooter.myKillsCountTable[target.id] = shooter.myKillsCountTable[target.id]+1;
            HUD.DisplayKillMessage(target:GetName(), shooter.myKillsCountTable[target.id], teamkill, true, playerId==shooterId, points);
            if (points~=0 and self:DisplayKillScores()) then
                HUD.DisplayFunMessage(tostring(points));
            end
        end
    end
    
    if(playerId == g_localActorId and self:ShouldAutoRespawn()) then
		-- don't do this for non-local players
		local forceRespawnTime = System.GetCVar("g_spawn_force_timeout")*1000;
		self:SetTimer(self.FORCERESPAWN_TIMERID, forceRespawnTime);
	end
end

----------------------------------------------------------------------------------------------------

function InstantAction:ShouldAutoRespawn()
	return true;
end


-- =============================================================================
-- Function: InstantAction:ShatterEntity
--
-- This function is called when an entity is shattered.
--
-- \param[in] entityId
-- The id of the entity that is being shattered.
--
-- \param[in] hit
-- Data structure containing all the information about the hit.
-- =============================================================================
function InstantAction:ShatterEntity(entityId, hit)
    local entity=System.GetEntity(entityId);
    local dead=false;
    if (entity) then
        if (entity.IsDead) then
            dead=entity:IsDead();
        end

        if (hit.shooterId==entityId) then
            if (entity.frostShooterId) then
                hit.shooterId=entity.frostShooterId;
                hit.shooter=System.GetEntity(entity.frostShooterId);
            end
        end

        if (entity.actor and entity.actor:IsPlayer() and (not dead)) then
            entity.death_time=_time;
            entity.death_pos=entity:GetWorldPos(entity.death_pos);
            self.game:KillPlayer(entityId, false, false, hit.shooterId, hit.weaponId, hit.damage, hit.materialId, hit.typeId, hit.dir);
        end
    end

    local damage=math.min(100, hit.damage or 0);
    damage=math.max(20, damage);
    self.game:ShatterEntity(entityId, hit.pos, vecScale(hit.dir, damage));

    -- do the scores here
    if (entity) then
        if (entity.actor and entity.actor:IsPlayer() and (not dead)) then
            self:ProcessScores(hit);
        end
    end

    if (entity.Server and entity.Server.OnShattered) then
        entity.Server.OnShattered(entity, hit);
    end
end


-- =============================================================================
-- Function: InstantAction:KillPlayer
--
-- This function kills a player.
--
-- \param[in] player
-- The object of the player to be killed.
-- =============================================================================
function InstantAction:KillPlayer(player)
    local hit = self.killHit;
    hit.pos=player:GetWorldPos(self.killHit.pos);
    hit.dir=g_Vectors.v000;
    hit.radius = 0;
    hit.partId = -1;
    hit.target = player;
    hit.targetId = player.id;
    hit.weapon = player;
    hit.weaponId = player.id
    hit.shooter = player;
    hit.shooterId = player.id
    hit.materialId = 0;
    hit.damage = 0;
    hit.typeId = self.game:GetHitTypeId("normal");
    hit.type = "normal";

    self:ProcessDeath(hit);
end


-- =============================================================================
-- Function: InstantAction:ProcessDeath
--
-- This function is called to process the death of a player.
--
-- \param[in] hit
-- Data structure containing all the information about the hit.
-- =============================================================================
function InstantAction:ProcessDeath(hit)
    self.Server.OnPlayerKilled(self, hit);
end



-- =============================================================================
-- Function: InstantAction:ResetScore
--
-- This function resets a players score.
--
-- \param[in] playerId
-- The id of the player whose scores are to be reset.
-- =============================================================================
function InstantAction:ResetScore(playerId)
    self.game:SetSynchedEntityValue(playerId, self.SCORE_KILLS_KEY, 0);
    self.game:SetSynchedEntityValue(playerId, self.SCORE_DEATHS_KEY, 0);
    self.game:SetSynchedEntityValue(playerId, self.SCORE_HEADSHOTS_KEY, 0);
    CryAction.SendGameplayEvent(playerId, eGE_ScoreReset, "", 0);
end


-- =============================================================================
-- Function: InstantAction:GetPlayerScore
--
-- \param[in] playerId
-- The id of the player to get the score from.
--
-- \return
-- The score of the specified player.
-- =============================================================================
function InstantAction:GetPlayerScore(playerId)
    return self.game:GetSynchedEntityValue(playerId, self.SCORE_KILLS_KEY, 0) or 0;
end


-- =============================================================================
-- Function: InstantAction:GetPlayerDeaths
--
-- \param[in] playerId
-- The id of the player to get the deaths from.
--
-- \return
-- The number of deaths for the specified player.
-- =============================================================================
function InstantAction:GetPlayerDeaths(playerId)
    return self.game:GetSynchedEntityValue(playerId, self.SCORE_DEATHS_KEY, 0) or 0;
end

-- =============================================================================
-- Function: InstantAction:GetPlayerKills
--
-- Added in SSM AEGIS 1.2.
--
-- \param[in] playerId
-- The id of the player to get the kills from.
--
-- \return
-- The number of kills for the specified player.
-- =============================================================================
function InstantAction:GetPlayerKills(playerId)
    return self.game:GetSynchedEntityValue(playerId, self.SCORE_KILLS_KEY) or 0;
end

-- =============================================================================
-- Function: InstantAction:GetPlayerHeadshots
--
-- Added in SSM AEGIS 1.2.
--
-- \param[in] playerId
-- The id of the player to get the headshots from.
--
-- \return
-- The number of headshots for the specified player.
-- =============================================================================
function InstantAction:GetPlayerHeadshots(playerId)
    return self.game:GetSynchedEntityValue(playerId, self.SCORE_HEADSHOTS_KEY) or 0;
end

-- =============================================================================
-- Function: InstantAction:Award
--
-- This function updates the client for the number of deaths, kills, and
-- headshots he currently has.
--
-- \param[in] player
-- The object of the player to update.
--
-- \param[in] deaths
-- The number of deaths the player has.
--
-- \param[in] kills
-- The number of kills the player has.
--
-- \param[in] headshots
-- The number of headshots the player has.
-- =============================================================================
function InstantAction:Award(player, deaths, kills, headshots)
    if (player) then
        local ckills=kills + (self.game:GetSynchedEntityValue(player.id, self.SCORE_KILLS_KEY) or 0);
        local cdeaths=deaths + (self.game:GetSynchedEntityValue(player.id, self.SCORE_DEATHS_KEY) or 0);
        local cheadshots=headshots + (self.game:GetSynchedEntityValue(player.id, self.SCORE_HEADSHOTS_KEY) or 0);

        --
        -- Set the values to the client using the synched entity keys.
        --
        self.game:SetSynchedEntityValue(player.id, self.SCORE_KILLS_KEY, ckills);
        self.game:SetSynchedEntityValue(player.id, self.SCORE_DEATHS_KEY, cdeaths);
        self.game:SetSynchedEntityValue(player.id, self.SCORE_HEADSHOTS_KEY, cheadshots);

        if (kills and kills~=0) then
            CryAction.SendGameplayEvent(player.id, eGE_Scored, "kills", ckills);
        end

        if (deaths and deaths~=0) then
            CryAction.SendGameplayEvent(player.id, eGE_Scored, "deaths", cdeaths);
        end

        if (headshots and headshots~=0) then
            CryAction.SendGameplayEvent(player.id, eGE_Scored, "headshots", cheadshots);
        end

        if (self.CheckPlayerScoreLimit) then
            self:CheckPlayerScoreLimit(player.id, ckills);
        end
        
        if ( XCfgVar.useSendKDRToPlayerFeature ) then
			if (cdeaths==0) then
				XSendChatToTarget(nil, player, "Kill/Death Ratio (%d/0): %d ", ckills, ckills);
			else
				local newKDR = ckills/cdeaths;
				XSendChatToTarget(nil, player, "Kill/Death Ratio (%d/%d): %2.2f", ckills, cdeaths, newKDR);
			end
        end
    end
end


-- =============================================================================
-- Function: InstantAction:ProcessScores
--
-- This function processes the scores of players while the game state is InGame.
--
-- \param[in] hit
-- Data structure containing all the information about the hit.
-- =============================================================================
function InstantAction:ProcessScores(hit)
    if (self:GetState()=="PostGame") then return; end

    local target=hit.target;
    local shooter=hit.shooter;
    local headshot=self:IsHeadShot(hit);

    local h=0;
    if (headshot) then
        h=1;
    end

    if (target.actor and target.actor:IsPlayer()) then
        self:Award(target, 1, 0, 0);
    end

    if (shooter and shooter.actor and shooter.actor:IsPlayer()) then
        if (target ~= shooter) then
            self:Award(shooter, 0, 1, h);
        else
            self:Award(shooter, 0, -1, 0);
        end
    end
end


-- =============================================================================
-- Function: InstantAction:ProcessVehicleScores
--
-- \param[unused] targetId
-- \param[unused] shooterId
-- =============================================================================
function InstantAction:ProcessVehicleScores(targetId, shooterId)
end


-- =============================================================================
-- Function: InstantAction.Server:RequestRevive
--
-- This function requests a revive of the player if the player has been dead for
-- more than 2.5 seconds.
--
-- \param[in] entityId
-- The id of the player requesting to be revived.
-- =============================================================================
function InstantAction.Server:RequestRevive(entityId)
    local player = System.GetEntity(entityId);

    if (player and player.actor) then
        if (player.death_time and _time-player.death_time>2.5 and player:IsDead()) then
            self:RevivePlayer(player.actor:GetChannel(), player);
        end
    end
end


-- =============================================================================
-- Function: InstantAction.Server:RequestSpawnGroup
--
-- This function requests a spawn group for a player.
--
-- \param[in] playerId
-- The id of the player.
--
-- \param[in] groupId
-- The id of the spawn group being requested.
--
-- \param[in] force
-- If true forces the spawn group on the player.
-- =============================================================================
function InstantAction.Server:RequestSpawnGroup(playerId, groupId, force)
    local player=System.GetEntity(playerId);
    if (player) then
        local teamId=self.game:GetTeam(playerId);

        if ((not force) and (teamId ~= self.game:GetTeam(groupId))) then
            return;
        end

        if (groupId==player.spawnGroupId) then
            return;
        end

        local group=System.GetEntity(groupId);
        if (group and group.vehicle and (group.vehicle:IsDestroyed() or group.vehicle:IsSubmerged())) then
            return;
        end

        if (group and group.vehicle) then
            local vehicle=group.vehicle;
            local seats=group.Seats;
            local seatCount = 0;

            for i,v in pairs(seats) do
                if ((not v.seat:IsGunner()) and (not v.seat:IsDriver()) and (not vehicle.x_lock)) then
                    seatCount=seatCount+1;
                end
            end

            local occupied=0;
            local players=self.game:GetPlayers(true);
            local mateGroupId;

            if (players) then
                for i,player in pairs(players) do
                    if (teamId==self.game:GetTeam(player.id)) then
                        mateGroupId=self:GetPlayerSpawnGroup(System.GetEntity(player.id)) or NULL_ENTITY;
                        if (mateGroupId==groupId) then
                            occupied=occupied+1;
                        end
                    end
                end
            end

            if (occupied>=seatCount) then
                return;
            end
        end

        self:SetPlayerSpawnGroup(playerId, groupId);

        if ((not g_localActorId) or (g_localActorId~=playerId)) then
            local channelId=player.actor:GetChannel();

            if (channelId and channelId>0) then
                self.onClient:ClSetSpawnGroup(channelId, groupId);
            end
        end

        self:UpdateSpawnGroupSelection(player.id);
    end
end


-- =============================================================================
-- Function: InstantAction:UpdateSpawnGroupSelection
--
-- This function updates the spawn group selection for the player.
--
-- \param[in] playerId
-- The id of the player to be updated.
-- =============================================================================
function InstantAction:UpdateSpawnGroupSelection(playerId)
    local teamId=self.game:GetTeam(playerId);
    local players;
    if (teamId==0) then
        players=self.game:GetPlayers(true);
    else
        players=self.game:GetTeamPlayers(teamId, true);
    end

    if (players) then
        local groupId=self:GetPlayerSpawnGroup(System.GetEntity(playerId)) or NULL_ENTITY;
        for i,player in pairs(players) do
            if (player.id~=playerId) then
                local channelId=player.actor:GetChannel();

                if (channelId and channelId>0) then
                    self.onClient:ClSetPlayerSpawnGroup(channelId, playerId, groupId);
                end
            end
        end
    end
end


-- =============================================================================
-- Function: InstantAction:UpdateSpawnGroupSelectionForPlayer
--
-- This function updates the spawn group selection for the player.
--
-- \param[in] playerId
-- The id of the player.
--
-- \param[in] teamId
-- The id of the team the player is on.
-- =============================================================================
function InstantAction:UpdateSpawnGroupSelectionForPlayer(playerId, teamId)
    local oPlayer=System.GetEntity(playerId);
    local players=self.game:GetPlayers(true);

    if (players) then
        local channelId=oPlayer.actor:GetChannel();
        if (channelId and channelId>0) then
            for i,player in pairs(players) do
                if (player.id~=playerId) then
                    local groupId=NULL_ENTITY;
                    if (teamId==self.game:GetTeam(player.id)) then
                        groupId=self:GetPlayerSpawnGroup(System.GetEntity(player.id)) or NULL_ENTITY;
                    end
                    self.onClient:ClSetPlayerSpawnGroup(channelId, player.id, groupId);
                end
            end
        end
    end
end


-- =============================================================================
-- Function: InstantAction.Server:RequestSpectatorTarget
--
-- This function requests a target for a spectating player.
--
-- \param[in] playerId
-- The id of the player making the target request.
--
-- \param[in] change
--
-- =============================================================================
function InstantAction.Server:RequestSpectatorTarget(playerId, change)
    
    --
    -- Do not allow a player to change spectator targets if he has been dead for
    -- less than 2.5 seconds.
    --
    local player = System.GetEntity(playerId);
    if(player.death_time and _time-player.death_time < 2.5) then
        return;
    end
    
    --
    -- Get the next spectator target for the player and set the spectate mode to
    -- that target. If the target id of the next target is 0 then there is no
    -- more targets to spectate so change back to free look mode.
    --
    local targetId = self.game:GetNextSpectatorTarget(playerId, change);
    if(targetId) then
        if(targetId~=0) then
            self.game:ChangeSpectatorMode(playerId, 3, targetId);
        elseif(self.game:GetTeam(playerId) == 0) then
            self.game:ChangeSpectatorMode(playerId, 1, NULL_ENTITY);
        end
    end
end


-- =============================================================================
-- Function: InstantAction.Server:OnSpawnGroupInvalid
--
-- This function is called when a players spawn group becomes invalid.
--
-- \param[in] playerId
-- The id of the player.
--
-- \param[in] spawnGroupId
-- The id of the spawn group that went invalid.
-- =============================================================================
function InstantAction.Server:OnSpawnGroupInvalid(playerId, spawnGroupId)
    local teamId=self.game:GetTeam(playerId) or 0;
    self.Server.RequestSpawnGroup(self, playerId, NULL_ENTITY, true);

    local player=System.GetEntity(playerId);
    if (player) then
        self.onClient:ClSpawnGroupInvalid(player.actor:GetChannel(), spawnGroupId);
    end
end


-- =============================================================================
-- Function: InstantAction.Client:ClSetupPlayer
--
-- This function is called on the client to set up the player.
--
-- \param[in] playerId
-- The id of the player to be setup.
-- =============================================================================
function InstantAction.Client:ClSetupPlayer(playerId)
    self:SetupPlayer(System.GetEntity(playerId));
end


-- =============================================================================
-- Function: InstantAction.Client:ClSpawnGroupInvalid
--
-- This function is called on the client when the players spawn group becomes
-- invalid. This is only done if the player is dead.
--
-- \param[in] spawnGroupId
-- The id of the spawn group that went invalid.
-- =============================================================================
function InstantAction.Client:ClSpawnGroupInvalid(spawnGroupId)
    if (HUD and g_localActor and g_localActor:IsDead()) then
        --HUD.OpenPDA(true, false);
        HUD.SpawnGroupInvalid();
    end
end


-- =============================================================================
-- Function: InstantAction.Client:ClSetSpawnGroup
--
-- This function is called on the client to set a new spawn group.
--
-- \param[in] groupId
-- The if of the new spawn group.
-- =============================================================================
function InstantAction.Client:ClSetSpawnGroup(groupId)
    if (g_localActor) then
        g_localActor.spawnGroupId=groupId;
    end
end


-- =============================================================================
-- Function: InstantAction.Client:ClSetPlayerSpawnGroup
--
-- This function is called on the client to set a new spawn group for the player.
--
-- \param[in] playerId
-- The id of the player.
--
-- \param[in] groupId
-- The id of the new group to set on the player.
-- =============================================================================
function InstantAction.Client:ClSetPlayerSpawnGroup(playerId, groupId)
    local player=System.GetEntity(playerId);
    if (player) then
        player.spawnGroupId=groupId;
    end
end


-- =============================================================================
-- Function: InstantAction.Server:OnItemPickedUp
--
-- This function is called when an item is picked up.
--
-- \param[in] itemId
-- The id of the item being picked up.
--
-- \param[in] actorId
-- The id of the player that picked up the item.
-- =============================================================================
function InstantAction.Server:OnItemPickedUp(itemId, actorId)
    self.game:AbortEntityRemoval(itemId);
    local item = System.GetEntity(itemId);
    local player = System.GetEntity(actorId);
    if (item and player and (player.class=="Player")) then
        if (XCfgVar.usePickupDebugFeature) then
            LogInfo("[AEGIS] %q picked up %s[%s]",player:GetName(),item:GetName(), item.class);
        end

        --
        -- If the player is in the penalty box then do not allow them to pick
        -- up any items. Immediately destroy the item from their inventory.
        --
		local profile = XGetIdentityValue(player, "profile");
		if (profile~="0") then
			if (AEGIS.PlayerInformationByProfile[profile] and AEGIS.PlayerInformationByProfile[profile].PenaltyBox) then
	            player.inventory:RemoveItem(itemId);
	            System.RemoveEntity(itemId);
	            self.game:SendTextMessage(TextMessageError, "Picking up weapons denied while in PENALTY BOX.", TextMessageToClient, player.id);
	        end
		end
    end
end


-- =============================================================================
-- Function: InstantAction:CanEnterVehicle
--
-- \param[in] vehicle
-- The object for the vehicle.
--
-- \param[in] userId
-- The id of the player wanting to enter the vehicle.
--
-- \return
-- True if the player can enter the vehicle.
-- =============================================================================
function InstantAction:CanEnterVehicle(vehicle, userId)
    local player = System.GetEntity(userId);
    if (player and (player.class=="Player")) then
        --
        -- Do not allow a player in the penalty box to enter a vehicle.
        --
		local profile = XGetIdentityValue(player, "profile");
		if (profile~="0") then
			if (AEGIS.PlayerInformationByProfile[profile] and AEGIS.PlayerInformationByProfile[profile].PenaltyBox) then
	            self.game:SendTextMessage(TextMessageError, "Use of vehicles denied while in PENALTY BOX.", TextMessageToClient, player.id);
	            return false;
	        end
	        if (vehicle.x_lock and vehicle.x_lock~=userId) then
	            XSendChatToTarget(nil, player, "Vehicle is currently locked!");
	            return false;
			end
		end
        
    end
    return true;
end


-- =============================================================================
-- Function: InstantAction.Server:OnItemDropped
--
-- This function is called when an item is dropped. It schedules the item to
-- be removed from the game based on the weapon abandoned time.
--
-- \param[in] itemId
-- The id of the item that was dropped.
--
-- \param[in] actorId
-- The id of the player that dropped the item.
-- =============================================================================
function InstantAction.Server:OnItemDropped(itemId, actorId)
    self.game:ScheduleEntityRemoval(itemId, self.WEAPON_ABANDONED_TIME, false);
end


-- =============================================================================
-- Function: InstantAction:GetServerStateTable
--
-- \return
-- This function returns the server state table for the current game state.
-- =============================================================================
function InstantAction:GetServerStateTable()
    local s=self:GetState();
    return self.Server[s];
end


-- =============================================================================
-- Function: InstantAction:GetClientStateTable
--
-- \return
-- This function returns the state table from the client.
-- =============================================================================
function InstantAction:GetClientStateTable()
    return self.Client[self:GetState()];
end


-- =============================================================================
-- Function: InstantAction:OnTick
--
-- This function is the event handler called whenever a tick event occurs.
-- =============================================================================
function InstantAction:OnTick()
    local onTick=self:GetServerStateTable().OnTick;
    if (onTick) then
        onTick(self);
    end
end


-- =============================================================================
-- Function: InstantAction:OnClientTick
--
-- This function is the event handler called whenever a tick event occurs.
-- =============================================================================
function InstantAction:OnClientTick()
    local onTick=self:GetClientStateTable().OnTick;
    if (onTick) then
        onTick(self);
    end
end


-- =============================================================================
-- Function: InstantAction:ResetPlayers
--
-- This function resets the score of all players.
-- =============================================================================
function InstantAction:ResetPlayers()
    local players=self.game:GetPlayers();
    if (players) then
        for i,player in pairs(players) do
            self:ResetScore(player.id);
        end
    end
end


-- =============================================================================
-- Function: InstantAction:ReviveAllPlayers
--
-- This function revives all the players.
--
-- \param[in] keepEquip
-- =============================================================================
function InstantAction:ReviveAllPlayers(keepEquip)
    local players=self.game:GetPlayers();

    if (players) then
        for i,player in ipairs(players) do
            if (player and player.actor and player.actor:GetSpectatorMode()==0) then
                self:RevivePlayer(player.actor:GetChannel(), player, keepEquip);
            end
        end
    end
end


-- =============================================================================
-- Function: InstantAction:DefaultState
--
-- This function sets the default state for the specified state.
--
-- \param[in] cs
-- Either "Server" or "Client"
--
-- \param[in] state
-- Either "Reset", "PreGame", "InGame", or "PostGame"
-- =============================================================================
function InstantAction:DefaultState(cs, state)
    local default=self[cs];
    self[cs][state] =
    {
        OnClientConnect         = default.OnClientConnect,
        OnClientDisconnect      = default.OnClientDisconnect,
        OnClientEnteredGame     = default.OnClientEnteredGame,
        OnDisconnect            = default.OnDisconnect, -- client only
        OnActorAction           = default.OnActorAction, -- client only
        OnStartLevel            = default.OnStartLevel,
        OnStartGame             = default.OnStartGame,

        OnKill                  = default.OnKill,
        OnHit                   = default.OnHit,
        OnFreeze                = default.OnFreeze,
        OnExplosion             = default.OnExplosion,
        OnChangeTeam            = default.OnChangeTeam,
        OnChangeSpectatorMode   = default.OnChangeSpectatorMode,
        RequestSpectatorTarget  = default.RequestSpectatorTarget,
        OnSetTeam               = default.OnSetTeam,
        OnItemPickedUp          = default.OnItemPickedUp,
        OnItemDropped           = default.OnItemDropped,

        OnTimer                 = default.OnTimer,
        OnUpdate                = default.OnUpdate,
    }
end

-- =============================================================================
-- Declares the default states for each game mode for client and server.
-- =============================================================================
InstantAction:DefaultState("Server", "Reset");
InstantAction:DefaultState("Client", "Reset");

InstantAction:DefaultState("Server", "PreGame");
InstantAction:DefaultState("Client", "PreGame");

InstantAction:DefaultState("Server", "InGame");
InstantAction:DefaultState("Client", "InGame");

InstantAction:DefaultState("Server", "PostGame");
InstantAction:DefaultState("Client", "PostGame");

InstantAction.Server.PostGame.OnChangeTeam = nil;
InstantAction.Server.PostGame.OnChangeSpectatorMode = nil;


-- =============================================================================
-- Function: InstantAction.Client.PreGame:OnBeginState
-- =============================================================================
function InstantAction.Client.PreGame:OnBeginState()
    self:StartTicking(true);
end


-- =============================================================================
-- Function: InstantAction.Server.PreGame:OnBeginState
-- =============================================================================
function InstantAction.Server.PreGame:OnBeginState()
    self:ResetTime();
    self:StartTicking();
    self:ResetPlayers();

    self.starting=false;
    self.warningTimer=0;
end


-- =============================================================================
-- Function: InstantAction.Server.PreGame:OnUpdate
-- =============================================================================
function InstantAction.Server.PreGame:OnUpdate(frameTime)
    InstantAction.Server.InGame.OnUpdate(self, frameTime);
end


-- =============================================================================
-- Function: InstantAction.Server.PreGame:OnTick
--
-- This function is called when a "tick" event happens in the PreGame state.
-- =============================================================================
function InstantAction.Server.PreGame:OnTick()
    if (self:PlayerCountOk()) then
        if (not self.starting) then
            self.starting=true;
            self.game:ResetGameStartTimer(System.GetCVar("g_roundRestartTime"));
        end
    elseif (self.starting) then
        self.starting=false;
        self.warningTimer=0;

        self.game:ResetGameStartTimer(-1);
    end

    if (self.starting) then
        if (self.game:GetRemainingStartTimer()<=0) then
            self.starting=false;

            self:RestartGame(true);
        end
    else
        self.warningTimer = self.warningTimer-1;
        if (self.warningTimer<=0) then
            self.game:SendTextMessage(TextMessageCenter, "@mp_MinPlayerWarning", TextMessageToAll, nil, self.game:GetMinPlayerLimit());
            self.warningTimer=self.MIN_PLAYER_LIMIT_WARN_TIMER;
        end
    end
end


-- =============================================================================
-- Function: InstantAction.Client.PreGame:OnTick
--
-- This function is called when a "tick" event happens in the PreGame state.
-- =============================================================================
function InstantAction.Client.PreGame:OnTick()
    local time=math.floor(self.game:GetRemainingStartTimer()+0.5);
    if (time>0) then
        -- new format for these. Send time to game start as p0, score limit as p1, time limit as p2.
        if(self.USE_SPAWN_GROUPS) then
            self.game:TextMessage(TextMessageBig,  "@mp_GameStarting_PS", time);
        else
            local scoreLimit = self.game:GetFragLimit();
            if(self.game:GetTeamCount() ~= 0) then
                scoreLimit = self.game:GetScoreLimit();
        end
            local timeLimit = self.game:GetTimeLimit();

            if(scoreLimit ~= 0) then
                if(timeLimit ~= 0) then
                    -- score limit and time limit set
                    self.game:TextMessage(TextMessageBig,  "@mp_GameStarting_4", time, scoreLimit, timeLimit);
                else
                    -- score limit, no time limit
                    self.game:TextMessage(TextMessageBig,  "@mp_GameStarting_1", time, scoreLimit);
                end
            else
                if(timeLimit ~= 0) then
                    -- time limit, no score limit
                    self.game:TextMessage(TextMessageBig,  "@mp_GameStarting_2", time, scoreLimit, timeLimit);
                else
                    -- no time limit or score limit
                    self.game:TextMessage(TextMessageBig,  "@mp_GameStarting_3", time);
                end
            end
        end
    end
end


-- =============================================================================
-- Function: InstantAction.Server.InGame:OnTick
--
-- This function is called when a "tick" event happens in the InGame state.
-- =============================================================================
function InstantAction.Server.InGame:OnTick()
--  if (not System.IsDevModeEnable()) then
--      if (not self:PlayerCountOk()) then
--          self:GotoState("Reset");
--          self.game:ResetEntities();
--      end
--  end
end


-- =============================================================================
-- Function: InstantAction.Server.InGame:OnBeginState
-- =============================================================================
function InstantAction.Server.InGame:OnBeginState()
    self:ResetTime();
    self:StartTicking();
    self:ResetPlayers();

    CryAction.SendGameplayEvent(NULL_ENTITY, eGE_GameStarted, "", 1);--server
end


-- =============================================================================
-- Function: InstantAction.Client.InGame:OnBeginState
-- =============================================================================
function InstantAction.Client.InGame:OnBeginState()
    self:StartTicking(true);

    CryAction.SendGameplayEvent(NULL_ENTITY, eGE_GameStarted, "", 0);--client
end


-- =============================================================================
-- Function: InstantAction.Server.InGame:OnUpdate
-- =============================================================================
function InstantAction.Server.InGame:OnUpdate(frameTime)
    InstantAction.Server.OnUpdate(self, frameTime);

    self:CheckTimeLimit();
    self:UpdateClAlerts();
end


-- =============================================================================
-- Function: InstantAction.Server.PostGame:OnBeginState
-- =============================================================================
function InstantAction.Server.PostGame:OnBeginState()
    CryAction.SendGameplayEvent(NULL_ENTITY, eGE_GameEnd, "", 1);--server

    self:StartTicking();
    self:SetTimer(self.NEXTLEVEL_TIMERID, self.NEXTLEVEL_TIME);
end


-- =============================================================================
-- Function: InstantAction.Client.PostGame:OnBeginState
-- =============================================================================
function InstantAction.Client.PostGame:OnBeginState()
    CryAction.SendGameplayEvent(NULL_ENTITY, eGE_GameEnd, "", 0);--client

    self:StartTicking(true);
    self:SetTimer(self.ENDGAME_TIMERID, self.ENDGAME_TIME);
end


-- =============================================================================
-- Function: InstantAction.Client.PostGame:OnEndState
-- =============================================================================
function InstantAction.Client.PostGame:OnEndState()
    self:EndGame(false);
end


-- =============================================================================
-- Function: InstantAction:EquipActor
--
-- This function does nothing.
-- =============================================================================
function InstantAction:EquipActor(actor)
end


-- =============================================================================
-- Function: InstantAction:CanWork
--
-- This function determines whether the work being request can be performed.
--
-- \param[in] entityId
-- \param[in] playerId
-- \param[in] work_type
-- =============================================================================
function InstantAction:CanWork(entityId, playerId, work_type)
    if (self.isServer) then
        local work=self.works[playerId];
        if (work) then
            if (work.active and (work.entityId~=entityId)) then -- disarming explosives will change work.type, but the weapon will keep reporting a different work_type
                return false;
            end
        end
    end

    local entity=System.GetEntity(entityId);
    if (entity) then
        if (work_type=="repair") then
            if (entity.vehicle) then
                local canRepair=entity.vehicle:IsRepairableDamage();
                if (self.game:IsSameTeam(entityId, playerId) or self.game:IsNeutral(entityId)) then
                    if (canRepair and (not entity.vehicle:IsDestroyed()) and (not entity.vehicle:IsSubmerged())) then
                        return true;
                    end
                end
            elseif (entity.item) then
                if (((entity.class == "AutoTurret") or (entity.class == "AutoTurretAA")) and (not entity.item:IsDestroyed())) then
                    local health=entity.item:GetHealth();
                    local maxhealth=entity.item:GetMaxHealth();
                    if ((health < maxhealth) and (not entity.item:IsDestroyed())) then
                        return true;
                    end
                end
            elseif (entity.CanDisarm and entity:CanDisarm(playerId)) then
                return true;
            end
        elseif (work_type=="lockpick") then
            if (entity.vehicle) then
                local v=entity.vehicle;
                if (((not self.game:IsSameTeam(entityId, playerId)) and (not self.game:IsNeutral(entityId))) or (entity.x_lock)) then
                    if (v:IsEmpty() and (not v:IsDestroyed())) then
                        return true;
                    end
                end
            end
        end
    end


    return false;
end


-- =============================================================================
-- Function: InstantAction:StartWork
--
-- This function
--
-- \param[in] entityId
-- \param[in] playerId
-- \param[in] work_type
-- =============================================================================
function InstantAction:StartWork(entityId, playerId, work_type)
    local work=self.works[playerId];
    if (not work) then
        work={};
        self.works[playerId]=work;
    end

    work.active=true;
    work.entityId=entityId;
    work.playerId=playerId;
    work.type=work_type;
    work.amount=0;
    work.complete=nil;

    --Log("%s starting '%s' work on %s...", EntityName(playerId), work_type, EntityName(entityId));

    -- HAX
    local entity=System.GetEntity(entityId);
    if (entity)then
        if (entity.CanDisarm and entity:CanDisarm(playerId)) then
            work_type="disarm";
            work.type=work_type;
        end
    end

    self.onClient:ClStartWorking(self.game:GetChannelId(playerId), entityId, work_type);
end


-- =============================================================================
-- Function: InstantAction:StopWork
--
-- This function
--
-- \param[in] playerId
--
-- =============================================================================
function InstantAction:StopWork(playerId)
    local work=self.works[playerId];
    if (work and work.active) then
        if (work.complete) then
            --Log("%s completed '%s' work on %s...", EntityName(playerId), work.type, EntityName(work.entityId));
        else
            --Log("%s stopping '%s' work on %s...", EntityName(playerId), work.type, EntityName(work.entityId));
        end
        work.active=false;

        self.onClient:ClStopWorking(self.game:GetChannelId(playerId), work.entityId, work.complete or false);

        if (work.complete) then
            self.allClients:ClWorkComplete(work.entityId, work.type);
        end
    end
end


-- =============================================================================
-- Function: InstantAction:Work
--
-- This function
--
-- \param[in] playerId
-- \param[in] amount
-- \param[in] frameTime
-- =============================================================================
function InstantAction:Work(playerId, amount, frameTime)
    local work=self.works[playerId];
    if (work and work.active) then
        --Log("%s doing '%s' work on %s for %.3fs...", EntityName(playerId), work.type, EntityName(work.entityId), frameTime);

        local entity=System.GetEntity(work.entityId);
        if (entity) then
            local workamount=amount*frameTime;
            if (work.type=="repair") then
                if (not self.repairHit) then
                    self.repairHit={
                        typeId  =self.game:GetHitTypeId("repair"),
                        type        ="repair",
                        material=0,
                        materialId=0,
                        dir         =g_Vectors.up,
                        radius  =0,
                        partId  =-1,
                    };
                end

                local hit=self.repairHit;
                hit.shooter=System.GetEntity(playerId);
                hit.shooterId=playerId;
                hit.target=entity;
                hit.targetId=work.entityId;
                hit.pos=entity:GetWorldPos(hit.pos);
                hit.damage=workamount;

                work.amount=work.amount+workamount;

                if (entity.vehicle) then
                    entity.Server.OnHit(entity, hit);
                    work.complete=(entity.vehicle:IsRepairableDamage() == false); -- keep working?

                    local progress=math.floor(0.5+(1.0-entity.vehicle:GetRepairableDamage())*100)
                    self.onClient:ClStepWorking(self.game:GetChannelId(playerId), progress);

                    return (not work.complete);
                elseif (entity.item and (entity.class=="AutoTurret" or entity.class=="AutoTurretAA") and (not entity.item:IsDestroyed())) then
                    entity.Server.OnHit(entity, hit);
                    work.complete=entity.item:GetHealth()>=entity.item:GetMaxHealth();

                    local progress=math.floor(0.5+(100*entity.item:GetHealth()/entity.item:GetMaxHealth()));
                    self.onClient:ClStepWorking(self.game:GetChannelId(playerId), progress);

                    return (not work.complete);
                end
            elseif (work.type=="lockpick") then
                work.amount=work.amount+workamount;

                if (work.amount>100) then
                    self.game:SetTeam(self.game:GetTeam(playerId), entity.id);
                    entity.vehicle:SetOwnerId(NULL_ENTITY);
                    entity.x_lock = nil;
                    work.complete=true;
                end

                self.onClient:ClStepWorking(self.game:GetChannelId(playerId), math.floor(work.amount+0.5));

                return (not work.complete);
            elseif (work.type=="disarm") then
                if (entity.CanDisarm and entity:CanDisarm(playerId)) then
                    work.amount=work.amount+(100/4)*frameTime;

                    if (work.amount>100) then
                        if (self.OnDisarmed) then
                            self:OnDisarmed(work.entityId, playerId);
                        end
                        System.RemoveEntity(work.entityId);
                        work.complete=true;
                    end

                    self.onClient:ClStepWorking(self.game:GetChannelId(playerId), math.floor(work.amount+0.5));

                    return (not work.complete);
                end
            end
        end
    end

    return false;
end


-- =============================================================================
-- Function: InstantAction.Client:ClStartWorking
--
-- This function
--
-- \param[in] entityId
-- \param[in] workName
-- =============================================================================
function InstantAction.Client:ClStartWorking(entityId, workName)
    self.work_type=workName;
    self.work_name="@ui_work_"..workName;
    HUD.SetProgressBar(true, 0, self.work_name);
end


-- =============================================================================
-- Function: InstantAction.Client:ClStepWorking
--
-- This function
--
-- \param[in] amount
--
-- =============================================================================
function InstantAction.Client:ClStepWorking(amount)
    HUD.SetProgressBar(true, amount, self.work_name or "");
end


-- =============================================================================
-- Function: InstantAction.Client:ClStopWorking
--
-- This function
--
-- \param[in] entityId
-- \param[in] complete
-- =============================================================================
function InstantAction.Client:ClStopWorking(entityId, complete)
    HUD.SetProgressBar(false, -1, "");
end


-- =============================================================================
-- Function: InstantAction.Client:ClWorkComplete
--
-- This function
--
-- \param[in] entityId
-- \param[in] workName
-- =============================================================================
function InstantAction.Client:ClWorkComplete(entityId, workName)
    local sound;
    if (workName=="repair") then
        sound="sounds/weapons:repairkit:repairkit_successful"
    elseif (workName=="lockpick") then
        sound="sounds/weapons:lockpick:lockpick_successful"
    end

    if (sound) then
        local entity=System.GetEntity(entityId);
        if (entity) then
            local sndFlags = SOUND_DEFAULT_3D;
            sndFlags = band(sndFlags, bnot(SOUND_OBSTRUCTION));
            sndFlags = bor(sndFlags, SOUND_LOAD_SYNCHRONOUSLY);

            local pos=entity:GetWorldPos(g_Vectors.temp_v1);
            pos.z=pos.z+1;

            return Sound.Play(sound, pos, sndFlags, SOUND_SEMANTIC_MP_CHAT);
        end
    end
end


-- =============================================================================
-- Function: InstantAction:EquipPlayer
--
-- This function
--
-- \param[in] actor
-- \param[in] additionalEquip
-- =============================================================================
-- TITAN
function InstantAction:EquipPlayer(actor, additionalEquip)
    if(self.game:IsDemoMode() ~= 0) then -- don't equip actors in demo playback mode, only use existing items
        Log("Don't Equip : DemoMode");
        return;
    end;

    actor.inventory:Destroy();

    ItemSystem.GiveItem("AlienCloak", actor.id, false);
    ItemSystem.GiveItem("OffHand", actor.id, false);
    ItemSystem.GiveItem("Fists", actor.id, false);

    if (additionalEquip and additionalEquip~="") then
        ItemSystem.GiveItemPack(actor.id, additionalEquip, true);
    end
	if (XIsPremium(actor) or XIsAdmin(actor)) then
   		XEquipPlayer(actor, XCfgVar.initialPlayerEquipmentPremium);
	else
   		XEquipPlayer(actor, XCfgVar.initialPlayerEquipment);
        end
end


-- =============================================================================
-- Function: InstantAction:PrecacheLevel
--
-- This function
--
--
-- =============================================================================
function InstantAction:PrecacheLevel()
    local list={ "SOCOM", "OffHand", "Fists" };

    for i,v in ipairs(list) do
        CryAction.CacheItemGeometry(v);
        CryAction.CacheItemSound(v);
    end
end


-- =============================================================================
-- Function: SetSpawnGroup
--
-- This function
--
-- \param[in] player
-- \param[in] group
-- =============================================================================
function SetSpawnGroup(player, group)
    local p;
    local gid;

    if (type(player)=="table") then
        p=player;
    elseif (type(player)=="userdata") then
        p=System.GetEntity(player);
    elseif (type(player)=="string") then
        p=EntityNamed(player);
    end

    if (type(group)=="table") then
        gid=group.id;
    elseif (type(player)=="userdata") then
        gid=group;
    elseif (type(player)=="string") then
        local ge=EntityNamed(group);
        if (ge) then
            gid=ge.id;
        end
    end

    if (p and gid) then
        g_gameRules:SetPlayerSpawnGroup(p, gid);
    end
end


-- =============================================================================
-- Function: InstantAction.Client:ClVictory
--
-- This function
--
-- \param[in] winningPlayerId
--
-- =============================================================================
function InstantAction.Client:ClVictory(winningPlayerId)
    if (winningPlayerId and winningPlayerId~=0) then
        if(winningPlayerId == g_localActorId) then
			self.game:GameOver(1, 0, winningPlayerId);
		else
			self.game:GameOver(-1, 0, winningPlayerId);
        end
    end
end


-- =============================================================================
-- Function: InstantAction.Client:ClClientConnect
--
-- This function
--
-- \param[in] name
-- \param[in] reconnect
-- =============================================================================
function InstantAction.Client:ClClientConnect(name, reconnect)
    if (reconnect) then
        HUD.BattleLogEvent(eBLE_Information, "@mp_BLPlayerReConnected", name);
    else
        HUD.BattleLogEvent(eBLE_Information, "@mp_BLPlayerConnected", name);
    end
end


-- =============================================================================
-- Function: InstantAction.Client:ClClientDisconnect
--
-- This function
--
-- \param[in] name
--
-- =============================================================================
function InstantAction.Client:ClClientDisconnect(name)
    HUD.BattleLogEvent(eBLE_Information, "@mp_BLPlayerDisconnected", name);
end


-- =============================================================================
-- Function: InstantAction.Client:ClClientEnteredGame
--
-- This function
--
-- \param[in] name
--
-- =============================================================================
function InstantAction.Client:ClClientEnteredGame(name)
    HUD.BattleLogEvent(eBLE_Information, "@mp_BLPlayerEnteredGame", name);
end


-- =============================================================================
-- Function: InstantAction.Client:ClNoWinner
--
-- This function
--
--
-- =============================================================================
function InstantAction.Client:ClNoWinner()
    self.game:GameOver(0);
end


-- =============================================================================
-- Function: InstantAction.Client:ClTimerAlert
--
-- This function
--
-- \param[in] time
--
-- =============================================================================
function InstantAction.Client:ClTimerAlert(time)
    if (not g_localActorId) then return end
    local teamId=self.game:GetTeam(g_localActorId);
    if (time==120) then
        self:PlayRadioAlert("timer2m", teamId);
    elseif(time==60) then
        self:PlayRadioAlert("timer1m", teamId);
    elseif(time==30) then
        self:PlayRadioAlert("timer30s", teamId);
    else
        self:PlayRadioAlert("timer5s", teamId);
    end
end
-- =============================================================================
function InstantAction:OnChatMessage(playerId, msg, teamChat)
    if((not msg) or msg==" ") then
        return " ";
    end
    local msg = tostring(msg);
    local source = System.GetEntity(playerId);
    if(not source) then
        return " ";
    end
    if(source:GetName()==XCfgVar.ChatEntityName) then
        return msg;
    end
    local chatMsg = {
		time = _time;
		sender = source;
		--target = XVar.scanForChatTarget;
		text = msg;
	};
	local newmsg = msg;
	local muteUnknownCommand = false;
	if (XCfgVar.useChatResponses) then
		muteUnknownCommand = XScanForChatResponse(chatMsg.sender, chatMsg);
	end
	if (XCfgVar.useChatCommands) then
		local XToHide = XScanForChatCommand(chatMsg.sender, chatMsg, muteUnknownCommand);
		if(XToHide and XCfgVar.HideChatCommands) then newmsg = ""; end
	end
    
    
    
    
    if(chatMsg.sender.XIsMuted==true) then
        --LogAdmins(msg);
        newmsg = " ";
        XSendConsoleMessageToPlayer(chatMsg.sender, XCfgVar.MutedMessage);
        XSendChatToTarget(nil, chatMsg.sender, XCfgVar.MutedMessage);
    end

    local match = false;
	for i,word in pairs(XCfgVar.BadWordList) do
        spos, i = string.find(string.lower(newmsg), string.lower(word));
		while (spos) do
			local before = string.sub (newmsg, 1, spos-1);
            local after = string.sub (newmsg, spos+string.len(word));
            match = true;

			newmsg = XFormat("%s%s%s",before,string.rep("*",string.len(word)),after);
            spos, i = string.find(string.lower(newmsg), string.lower(word));
		end
	end
	if(match) then
        XOnPlayerSwear(chatMsg.sender, msg);
    end
    
    
	local playerName = chatMsg.sender:GetName();
    if (teamChat) then
		chatMsg.type = ChatToTeam;
		if (chatMsg.sender) then
			local teamId = g_gameRules.game:GetTeam(chatMsg.sender.id);
			XSendConsoleMessageToTeamAndAdmins(teamId, "$9[CHT] $8%s$9 to $1TEAM$9: $5%s", playerName, newmsg);
		end
		LogAlways("[CHT] %s to TEAM: %s", playerName, chatMsg.text);
		SendAdmins("[CHT] %s to TEAM: Original(%s)", playerName, chatMsg.text);
		table.insert(XVar.chatMessages, chatMsg);
	else
		chatMsg.type = ChatToAll;
		if (chatMsg.sender) then
			XSendConsoleMessageToAll("$9[CHT] $8%s$9 to $1ALL$9: $5%s", playerName, newmsg);
		end
		LogAlways("[CHT] %s to ALL: %s", playerName, chatMsg.text);
		SendAdmins("[CHT] %s to ALL: Original(%s)", playerName, chatMsg.text);
		table.insert(XVar.chatMessages, chatMsg);
	end
	while (XVar.chatMessages and (#XVar.chatMessages>XCfgVar.chatMessagestoHold)) do
		table.remove(XVar.chatMessages, 1);
	end
	
	if (msg~=newmsg and XCfgVar.SendOriginalMsgToAdmins) then
	   XSendChatToAdmins('Original from '..playername..': "'..msg..'"');
    end
	
	
    
    return newmsg;
end


-- =============================================================================